Start HereOrdered learning track

Threat Model for Authentication

Learn Java Authentication Pattern - Part 003

Threat model autentikasi untuk engineer Java: credential theft, replay, phishing, session fixation, token substitution, confused deputy, tenant confusion, recovery abuse, dan bagaimana ancaman ini mengubah desain implementasi.

24 min read4783 words
PrevNext
Lesson 0340 lesson track01–08 Start Here
#java#authentication#security#threat-modeling+4 more

Part 003 — Threat Model for Authentication

Target part ini: kita membangun threat model autentikasi yang bisa langsung dipakai untuk desain, code review, testing, incident response, dan architecture review. Fokusnya bukan daftar serangan hafalan, tetapi bagaimana serangan mengubah invariant implementasi Java.

Authentication production-grade harus dimulai dari pertanyaan sederhana:

“Apa yang sebenarnya sedang kita lindungi, siapa yang mencoba memalsukannya, lewat boundary mana, dan bagaimana sistem gagal saat bukti identitas tidak lagi dapat dipercaya?”

Jika tim hanya mulai dari framework, desainnya biasanya seperti ini:

Spring Security + JWT + bcrypt + refresh token = secure

Itu bukan threat model. Itu daftar komponen.

Threat model yang lebih berguna:

asset -> adversary -> entry point -> attack path -> broken invariant -> detection -> prevention -> recovery

Authentication bukan hanya login. Authentication adalah sistem pembuktian identitas yang diserang dari banyak sisi:

  • credential dicuri,
  • session dicuri,
  • token di-replay,
  • redirect OAuth dimanipulasi,
  • user dipancing phishing,
  • reset password dipakai sebagai bypass,
  • header internal dipalsukan,
  • key signing bocor,
  • principal salah tenant,
  • SecurityContext bocor antar request atau async job,
  • audit identity salah sehingga insiden tidak bisa direkonstruksi.

Part ini membuat peta serangan sebelum kita masuk ke implementasi detail di part berikutnya.


1. Threat Model Autentikasi dalam Satu Kalimat

Authentication threat model menjawab:

“Dengan asumsi ada aktor jahat, jaringan tidak selalu aman, client tidak selalu jujur, user bisa tertipu, secret bisa bocor, dan dependency bisa salah konfigurasi, invariant apa yang harus tetap benar agar sistem tidak menerima identitas palsu?”

Invariant utama:

A request must not be treated as authenticated unless the system can verify valid, fresh, intended, non-replayed, non-confused evidence of identity at the current trust boundary.

Kata pentingnya:

KataMakna
verifyBukti harus diverifikasi, bukan dipercaya karena hadir.
validFormat, signature, credential, dan status harus benar.
freshBukti tidak boleh stale, expired, atau dari sesi lama yang tidak lagi sah.
intendedBukti harus dibuat untuk audience, client, redirect, tenant, dan flow ini.
non-replayedBukti tidak boleh bisa dipakai ulang oleh pencuri.
non-confusedBukti tidak boleh disalahartikan lintas client, service, tenant, atau protocol.
current trust boundaryValid di boundary A belum tentu valid di boundary B.

Authentication bug serius hampir selalu pelanggaran salah satu kata di atas.


2. Asset yang Dilindungi Authentication

Sebelum bicara serangan, tentukan asset.

AssetContohKenapa Penting
Credential secretpassword hash, API key hash, TOTP secret, private key, refresh tokenJika bocor, attacker bisa membuktikan identity.
Authentication artifactsession id, access token, ID token, authorization codeJika dicuri, attacker bisa bypass login.
Identity bindinguser ↔ tenant, device ↔ user, service ↔ workloadJika salah, request sah bisa diarahkan ke identity salah.
Verification keyJWK public key set, issuer config, trust anchor, CA bundleJika salah, token palsu bisa diterima.
Recovery channelemail reset, phone OTP, backup code, support overrideSering menjadi jalur bypass MFA/password.
Login statestate, nonce, PKCE verifier, CSRF token, login transactionJika dimanipulasi, flow OIDC/OAuth bisa dibajak.
Session stateserver session, remember-me token, refresh token familyJika tidak dikelola, logout/revocation gagal.
Security contextPrincipal di thread/request/eventJika bocor, kode downstream memakai identity salah.
Audit evidenceauth success/failure log, session id, actor id, IP, deviceJika salah, investigation dan compliance runtuh.

Mental model:

authentication protects not only the credential, but every artifact that can become proof of identity

Karena itu, menyimpan access token di log sama bahayanya dengan menyimpan password pada banyak skenario.


3. Adversary Model

Tidak semua attacker sama. Kontrol yang benar tergantung musuhnya.

AdversaryKemampuanContoh Serangan
Opportunistic botBanyak request murah, daftar password bocorcredential stuffing, password spraying
Targeted attackerMengenal korban tertentuphishing, SIM swap, MFA fatigue
Network attackerBisa melihat/memanipulasi traffic tertentutoken theft jika TLS gagal, redirect manipulation
Malicious clientMengontrol browser/mobile/app sendiriheader spoofing, token substitution
Compromised serviceSatu internal service sudah dikuasailateral movement, token relay abuse
Insider/admin abusePunya akses operasionalreset credential, impersonation tanpa audit
Supply-chain attackerMengubah dependency/build/deploymentcredential exfiltration, signing key theft
Tenant adversaryUser sah tenant A menyerang tenant Btenant confusion, cross-tenant token use

Aplikasi internal tidak kebal. Pada sistem enterprise, adversary paling berbahaya sering bukan internet bot, tetapi:

valid user + valid credential + wrong boundary + missing tenant invariant

4. Attack Surface Authentication

Authentication surface bukan hanya /login.

Threat model harus mencakup:

  • login,
  • registration,
  • password reset,
  • account recovery,
  • MFA enrollment,
  • MFA reset,
  • passkey registration,
  • session refresh,
  • logout,
  • token introspection,
  • token exchange,
  • OIDC callback,
  • service credential rotation,
  • admin impersonation,
  • support override,
  • internal header propagation,
  • background job execution,
  • audit event generation.

Banyak breach tidak terjadi lewat login utama. Terjadi lewat recovery, refresh, support tool, atau trust boundary internal.


5. Threat Modeling Loop yang Dipakai di Seri Ini

Untuk setiap authentication pattern, gunakan loop ini:

Checklist pertanyaan:

  1. Asset — apa yang jika bocor membuat identity bisa dipalsukan?
  2. Boundary — siapa yang boleh mengirim bukti ini?
  3. Evidence — apa bukti yang disajikan?
  4. Verification — bagaimana bukti diverifikasi?
  5. Binding — bukti ini terikat ke user, client, device, tenant, redirect URI, audience, dan session mana?
  6. Freshness — apa expiry, nonce, timestamp, one-time-use, atau rotation rule?
  7. Revocation — bagaimana bukti dibatalkan sebelum expired?
  8. Detection — sinyal apa yang menunjukkan abuse?
  9. Recovery — apa langkah aman saat credential/token/session/key bocor?

Jika satu pertanyaan tidak punya jawaban, desain belum production-grade.


6. Threat: Credential Theft

Credential theft terjadi saat bukti login dicuri.

Contoh:

  • password dicuri dari phishing,
  • password hash bocor dari database,
  • API key tersalin ke Git,
  • TOTP secret bocor dari backup,
  • refresh token dicuri dari local storage,
  • private key service dicuri dari container image,
  • database dump berisi recovery token plaintext.

6.1 Broken Invariant

Only the legitimate actor can present the credential.

Saat credential bocor, invariant itu mati. Sistem harus punya lapisan lain:

  • hashing lambat untuk password,
  • MFA/step-up,
  • device binding,
  • token rotation,
  • anomaly detection,
  • key rotation,
  • revocation,
  • blast-radius control.

6.2 Java Design Consequence

Credential tidak boleh diperlakukan sebagai string biasa.

Buruk:

record LoginRequest(String username, String password) {}

log.info("login request={}", request); // fatal: password bisa masuk log

Lebih aman:

public final class RawPassword {
    private final char[] value;

    private RawPassword(char[] value) {
        this.value = value;
    }

    public static RawPassword of(char[] value) {
        if (value == null || value.length == 0) {
            throw new IllegalArgumentException("password is required");
        }
        return new RawPassword(value.clone());
    }

    public char[] exposeForHashingOnly() {
        return value.clone();
    }

    public void erase() {
        java.util.Arrays.fill(value, '\0');
    }

    @Override
    public String toString() {
        return "[REDACTED]";
    }
}

Namun jujur secara engineering: di banyak aplikasi web Java, password sudah menjadi String saat JSON deserialization. Jadi kelas seperti ini bukan jaminan sempurna. Nilainya tetap berguna untuk:

  • mencegah accidental logging,
  • menegaskan semantic boundary,
  • membatasi API internal,
  • memaksa code review terhadap credential exposure.

6.3 Controls

ControlFungsi
Password hashing adaptifMembuat hash dump mahal di-crack.
Secret scanningMendeteksi API key/private key di repository.
MFA phishing-resistantMengurangi dampak password phishing.
Token rotationMembuat refresh token reuse terdeteksi.
Short-lived access tokenMembatasi durasi abuse setelah token bocor.
Credential versionMemungkinkan invalidasi semua token setelah credential reset.
Redacted loggingMencegah secret masuk log.
Envelope encryptionMengurangi dampak dump database untuk secret tertentu.

7. Threat: Credential Stuffing

Credential stuffing adalah penggunaan credential bocor dari tempat lain untuk mencoba login di sistem kita.

Ciri:

  • username/password valid dari breach eksternal,
  • volume tinggi,
  • IP tersebar,
  • user-agent beragam,
  • banyak failure tetapi sebagian kecil success,
  • target endpoint login dan refresh.

7.1 Broken Invariant

A correct password implies legitimate user presence.

Di dunia modern, password benar tidak cukup. Password benar bisa berarti:

user legitimate OR attacker has reused leaked credential

7.2 State Machine

7.3 Java Implementation Shape

Jangan letakkan rate limiting hanya setelah password verification. Password hashing mahal. Bot bisa membuat CPU exhaustion.

Pipeline lebih aman:

request normalization
-> cheap abuse gate by IP / username hash / device fingerprint
-> credential verification
-> risk evaluation
-> MFA/step-up if needed
-> session/token issuance

Contoh interface:

public interface LoginAbuseGate {
    AbuseDecision evaluate(LoginAttemptSignal signal);
}

public record LoginAttemptSignal(
        String usernameCanonical,
        String ipAddress,
        String userAgent,
        String deviceCookie,
        String tenantId,
        java.time.Instant occurredAt
) {}

public sealed interface AbuseDecision {
    record Allow() implements AbuseDecision {}
    record Delay(java.time.Duration duration) implements AbuseDecision {}
    record Challenge(String reason) implements AbuseDecision {}
    record Block(String reason) implements AbuseDecision {}
}

Important invariant:

rate limiting key must not be only IP and must not be only username

Hanya IP gagal karena botnet. Hanya username gagal karena attacker bisa lock user tertentu. Production biasanya memakai kombinasi:

  • IP prefix,
  • username canonical hash,
  • tenant,
  • ASN/geography,
  • device cookie,
  • failure velocity,
  • success anomaly,
  • global attack mode.

8. Threat: Password Spraying

Password spraying mencoba satu password umum ke banyak akun.

Contoh:

Summer2026! -> user1
Summer2026! -> user2
Summer2026! -> user3

Ini menghindari lockout per akun.

8.1 Broken Invariant

Low failure count per account means low risk.

Salah. Serangan tersebar perlu deteksi global.

8.2 Detection Signal

SignalArti
Satu password fingerprint gagal di banyak akunpassword spraying
Banyak username dari IP/ASN samabot campaign
Failure meningkat per tenanttargeted tenant attack
Banyak user not foundenumeration + spray
Banyak valid username + wrong passwordstuffing/spray terhadap account valid

Catatan: jangan log password plaintext. Untuk mendeteksi spraying berbasis password, bisa memakai fingerprint HMAC sementara dengan key khusus telemetry dan retention pendek.

public interface PasswordTelemetryFingerprint {
    String fingerprint(char[] rawPassword);
}

Rule:

fingerprint must not be reversible, must not be stored long-term, and must not use the password hashing store key

9. Threat: Brute Force

Brute force mencoba banyak kombinasi sampai benar.

Untuk web login manusia, brute force online seharusnya dihentikan oleh:

  • throttling,
  • lockout adaptif,
  • challenge,
  • MFA,
  • breach password screening,
  • monitoring.

Namun lockout bisa menjadi DoS.

9.1 Trade-off Lockout

StrategyKelebihanRisiko
Hard lock accountMenghentikan brute force per akunAttacker bisa lock banyak akun sah
Delay progresifLebih user-friendlyPerlu state store dan tuning
IP throttlingMurahLemah terhadap botnet/NAT
Username throttlingEfektif per akunBisa menjadi DoS targeted
Risk-based challengeLebih adaptifButuh sinyal risiko bagus
Tenant attack modeBagus saat campaignBisa mengganggu user normal

Production invariant:

controls must slow attackers without giving them a cheap account-lockout weapon

10. Threat: Account Enumeration

Account enumeration terjadi saat attacker bisa membedakan akun ada/tidak.

Contoh response buruk:

{ "error": "email not registered" }

atau:

user exists -> password hashing runs -> response 450ms
user missing -> immediate return -> response 20ms

10.1 Broken Invariant

The authentication endpoint does not reveal whether an identifier exists.

10.2 Error Semantics

Buruk:

Unknown email
Wrong password
Account disabled
MFA not enrolled

Lebih aman untuk public login:

Invalid username or password

Namun sistem internal tetap perlu alasan detail untuk audit. Jadi pisahkan:

external error: generic
internal audit reason: precise

Contoh:

public record LoginFailure(
        ExternalAuthError externalError,
        InternalFailureReason internalReason,
        String correlationId
) {}

enum ExternalAuthError {
    INVALID_CREDENTIALS,
    TEMPORARILY_UNAVAILABLE,
    ADDITIONAL_VERIFICATION_REQUIRED
}

enum InternalFailureReason {
    USER_NOT_FOUND,
    PASSWORD_MISMATCH,
    ACCOUNT_DISABLED,
    TENANT_DISABLED,
    PASSWORD_EXPIRED,
    RATE_LIMITED,
    RISK_BLOCKED
}

10.3 Timing Defense

Untuk user tidak ditemukan, lakukan dummy password verification dengan hash dummy yang cost-nya sama.

public final class PasswordVerifier {
    private final PasswordEncoder encoder;
    private final String dummyHash;

    public PasswordCheckResult verify(String username, char[] password) {
        UserCredential credential = credentialRepository.findByUsername(username)
                .orElse(null);

        String hashToCheck = credential != null ? credential.passwordHash() : dummyHash;
        boolean matches = encoder.matches(new String(password), hashToCheck);

        if (credential == null) {
            return PasswordCheckResult.unknownUser();
        }
        if (!matches) {
            return PasswordCheckResult.mismatch(credential.userId());
        }
        return PasswordCheckResult.success(credential.userId());
    }
}

Catatan: new String(password) tidak ideal karena membuat immutable string. Banyak library Java memang menerima CharSequence, sehingga trade-off harus dipahami. Part password storage akan membahas lebih detail.


11. Threat: Phishing

Phishing membuat user memberikan credential atau menyetujui challenge di situs/app palsu.

Yang dicuri bisa:

  • password,
  • OTP,
  • TOTP,
  • magic link,
  • session cookie,
  • OAuth authorization,
  • push approval,
  • recovery code.

11.1 Broken Invariant

The user can reliably distinguish legitimate verifier from attacker-controlled verifier.

Dalam praktik, user sering tidak bisa.

Karena itu, kontrol modern mengarah ke phishing-resistant authentication, terutama public-key based authenticator seperti WebAuthn/passkeys yang mengikat assertion ke origin.

11.2 MFA Tidak Selalu Phishing-Resistant

FaktorPhishing ResistanceCatatan
PasswordRendahBisa diketik di situs palsu.
Email OTPRendahBisa diminta lalu diteruskan attacker.
SMS OTPRendahRentan phishing dan SIM swap.
TOTPSedang/rendahKode bisa di-phish real-time.
Push approveSedangRentan fatigue/social engineering jika tanpa number matching.
WebAuthn/passkeyTinggiAssertion terikat origin dan private key tidak keluar.
mTLS client certTinggi untuk machineBergantung proteksi private key.

NIST SP 800-63B membedakan authenticator assurance level dan menekankan phishing resistance pada level assurance tinggi. OWASP juga menempatkan MFA sebagai kontrol penting, tetapi pilihan faktor menentukan kualitas keamanan.

11.3 Java Consequence

Jangan membuat desain MFA sebagai boolean:

boolean mfaEnabled;

Model yang lebih benar:

public record AuthenticatorEnrollment(
        String authenticatorId,
        String userId,
        AuthenticatorType type,
        PhishingResistance phishingResistance,
        java.time.Instant enrolledAt,
        java.time.Instant lastUsedAt,
        boolean active
) {}

enum AuthenticatorType {
    PASSWORD,
    TOTP,
    EMAIL_OTP,
    SMS_OTP,
    WEBAUTHN_PLATFORM,
    WEBAUTHN_ROAMING,
    CLIENT_CERTIFICATE
}

enum PhishingResistance {
    NONE,
    PARTIAL,
    STRONG
}

Authentication result harus membawa metode yang dipakai:

public record AuthenticationAssurance(
        int aal,
        boolean multiFactor,
        boolean phishingResistant,
        java.util.Set<AuthenticatorType> methods,
        java.time.Instant authenticatedAt
) {}

12. Threat: MFA Fatigue

MFA fatigue terjadi saat attacker yang sudah tahu password mengirim banyak push challenge sampai user menekan approve.

12.1 Broken Invariant

An approved MFA challenge implies user intent.

Approval bisa terjadi karena lelah, panik, atau tertipu.

12.2 Controls

ControlFungsi
Number matchingUser harus mencocokkan angka dari login screen.
Challenge contextTampilkan lokasi, device, app, dan waktu.
Rate limit challengeJangan izinkan spam push.
Deny reportingUser bisa melaporkan “this was not me”.
Step-up after denialDenial berulang menjadi sinyal compromise.
Prefer WebAuthnMengurangi push approval yang mudah disalahgunakan.

State machine:


13. Threat: Replay Attack

Replay terjadi saat bukti autentikasi yang sah dipakai ulang oleh attacker.

Contoh:

  • access token dicuri lalu dipakai ulang,
  • API request signature dipakai ulang karena tidak ada nonce,
  • authorization code dipakai dua kali,
  • password reset token tidak one-time-use,
  • WebAuthn challenge tidak invalidated,
  • refresh token lama masih diterima setelah rotation.

13.1 Broken Invariant

Evidence that was valid once is not necessarily valid again.

13.2 Replay Defense Matrix

ArtifactReplay Defense
Session idTLS, HttpOnly cookie, rotation on login, revocation.
Authorization codeShort expiry, one-time-use, PKCE binding.
Refresh tokenRotation, reuse detection, token family invalidation.
HMAC requestTimestamp, nonce, canonical request, replay cache.
Password reset tokenOne-time-use, short expiry, store hash only.
MFA challengeShort expiry, attempt count, challenge id, consumed flag.
WebAuthn challengeOrigin/RP ID verification, challenge one-time-use.

13.3 Java Pattern: One-Time Token Store

public interface OneTimeTokenStore {
    void issue(TokenPurpose purpose, String subjectId, String tokenHash, java.time.Instant expiresAt);

    ConsumeResult consume(TokenPurpose purpose, String tokenHash, java.time.Instant now);
}

public sealed interface ConsumeResult {
    record Consumed(String subjectId) implements ConsumeResult {}
    record NotFound() implements ConsumeResult {}
    record Expired() implements ConsumeResult {}
    record AlreadyConsumed() implements ConsumeResult {}
}

Database invariant:

-- concept only
unique(token_hash)
check(consumed_at is null or consumed_at >= issued_at)

Consumption harus atomic:

update one_time_token
set consumed_at = now()
where token_hash = :hash
  and purpose = :purpose
  and consumed_at is null
  and expires_at > now()
returning subject_id;

Jika memakai select lalu update terpisah tanpa lock/condition, dua request paralel bisa sama-sama lolos.


14. Threat: Session Fixation

Session fixation terjadi saat attacker membuat atau mengetahui session id sebelum korban login, lalu session itu tetap dipakai setelah korban authenticated.

Flow buruk:

14.1 Broken Invariant

Session identity after login must not be attacker-controlled from before login.

14.2 Controls

  • rotate session id after successful login,
  • clear anonymous pre-authentication attributes that are not explicitly allowed,
  • bind CSRF/login transaction state carefully,
  • invalidate session after logout,
  • prevent session id in URL,
  • use Secure + HttpOnly cookies,
  • avoid accepting session id from query parameter.

Spring Security has built-in session fixation protection for servlet applications, but custom login/session logic can bypass it if done incorrectly.

14.3 Java Review Point

Red flag:

request.getSession(true).setAttribute("USER_ID", userId);

without session id rotation.

Better conceptual flow:

anonymous session -> credential verified -> session id changed -> authenticated context stored -> old id invalid

15. Threat: Session Hijacking

Session hijacking terjadi saat attacker mendapatkan session id/cookie yang sudah authenticated.

Sumber:

  • XSS membaca token non-HttpOnly,
  • malware/browser extension,
  • insecure cookie flag,
  • TLS termination salah,
  • log berisi cookie,
  • session id di URL/referrer,
  • Redis/session store leak.

15.1 Broken Invariant

Possession of session id implies legitimate browser session.

Session id adalah bearer artifact. Siapa pun yang memegangnya bisa menjadi user, kecuali ada binding tambahan.

15.2 Controls

ControlCatatan
HttpOnlyMengurangi pencurian via JavaScript.
SecureCookie hanya via HTTPS.
SameSiteMengurangi cross-site request risk.
Rotation after loginMengurangi fixation.
Idle timeoutMembatasi stale session.
Absolute timeoutMembatasi sesi terlalu lama.
Device/session inventoryUser bisa melihat dan revoke session.
Server-side revocationLogout dan incident response efektif.
Anomaly detectionIP/ASN/device shift bisa trigger step-up.

16. Threat: CSRF Login and Forced Login

CSRF sering dibahas untuk aksi state-changing setelah login. Namun login juga bisa diserang.

Forced login:

attacker logs victim browser into attacker's account
victim performs actions thinking it is their own account
attacker observes results or manipulates data

16.1 Broken Invariant

The account in the browser session reflects the user's intent.

16.2 Controls

  • CSRF protection on login form,
  • state parameter in OIDC/OAuth,
  • clear existing session before login transition,
  • explicit account switch UX,
  • show authenticated identity clearly,
  • protect linking flows with reauthentication.

17. Threat: Token Theft

Token theft adalah pencurian access token, refresh token, ID token, API token, atau opaque reference token.

17.1 Common Root Causes

LocationRisk
Browser localStorageXSS can read bearer token.
Mobile insecure storageMalware/rooted device can extract.
Logs/APM tracesAuthorization header accidentally captured.
Reverse proxy logsHeader/body leakage.
URL fragments/queryToken leaks via referrer/history.
CI/CD variablesOver-permissive secrets.
DatabaseRefresh token stored plaintext.

17.2 Broken Invariant

Token possession implies legitimate actor.

Bearer token has no proof-of-possession. For many systems, this is accepted trade-off, but it must be explicit.

17.3 Controls

  • access token short TTL,
  • refresh token rotation,
  • store refresh tokens hashed,
  • no tokens in URLs,
  • redaction at ingress logs,
  • separate ID token vs access token,
  • validate issuer/audience/expiry/signature,
  • token binding/DPoP/mTLS for high-risk clients where appropriate,
  • revoke token family on reuse detection.

18. Threat: JWT Algorithm and Key Confusion

JWT verification bugs are common when teams treat JWT as “base64 JSON with signature”.

Examples:

  • accepting alg: none,
  • allowing algorithm chosen by token without policy,
  • confusing HMAC shared secret with RSA public key,
  • using wrong issuer JWK set,
  • trusting kid to fetch arbitrary key URL,
  • not validating aud,
  • using ID token as API access token,
  • accepting expired token due to clock skew too wide.

18.1 Broken Invariant

A signed token from any known key is valid for this API.

Correct invariant:

A token is accepted only if it is signed by the configured issuer's allowed algorithm/key, intended for this audience, not expired, not before valid, and semantically valid for this token type.

18.2 Verification Checklist

Claim/HeaderCheck
issExact expected issuer.
audContains this resource server/API audience.
expNot expired.
nbfNot before accepted.
iatReasonable age if policy requires.
subPresent and canonical.
typDistinguish access token vs ID token where policy uses type.
algAllowed by server config, not token preference.
kidSelects from trusted JWK set only.
azp/clientRequired in some multi-client OIDC scenarios.
tenant claimMust match tenant resolution boundary.

18.3 Java Rule

Do not parse JWT manually for authentication decisions.

Bad:

String[] parts = token.split("\\.");
String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
String userId = objectMapper.readTree(payload).get("sub").asText();

This extracts claims without verification.

Use a verifier configured with issuer, audience, algorithm/key policy, and clock policy. In Spring Security Resource Server, these concerns are represented by JWT decoder and validators. In Jakarta/MicroProfile environments, container or framework integration must still enforce equivalent checks.


19. Threat: Token Substitution

Token substitution occurs when a valid token for one context is accepted in another.

Examples:

  • ID token accepted as access token,
  • token for API A accepted by API B,
  • token for tenant A accepted in tenant B,
  • token from staging issuer accepted in production,
  • token for client mobile accepted by admin web app,
  • service token accepted as user token.

19.1 Broken Invariant

Valid token means valid here.

Correct:

valid token + correct type + correct issuer + correct audience + correct tenant + correct client + correct assurance + correct boundary

19.2 Decision Model


20. Threat: Confused Deputy

Confused deputy occurs when a privileged component is tricked into using its authority for the wrong actor or context.

Authentication examples:

  • API gateway authenticates user but internal service trusts spoofable X-User-Id,
  • service A calls service B with its own token but performs action for arbitrary user id from request,
  • backend exchanges token without preserving actor/client/audience constraints,
  • support tool impersonates user without audit and reason,
  • multi-tenant resolver uses path tenant but token tenant differs.

20.1 Broken Invariant

A trusted component's identity can safely stand in for the original actor.

Not always. You need delegation semantics.

20.2 Delegation Record

public record ActorChain(
        Actor initiatingActor,
        Actor authenticatingClient,
        Actor executingService,
        DelegationMode delegationMode
) {}

enum DelegationMode {
    NONE,
    USER_DELEGATED,
    SERVICE_ACTING_ON_BEHALF_OF_USER,
    ADMIN_IMPERSONATION,
    SYSTEM_AUTOMATION
}

Without actor chain, audit says:

pricing-service updated quote

But investigation needs:

user U initiated request via web-client C, gateway G authenticated it, pricing-service S executed it, using delegated user context, in tenant T

21. Threat: Tenant Confusion

Tenant confusion happens when identity, session, token, route, or data access refers to different tenants.

Examples:

sub=user-123, tenant=A
request path=/tenants/B/cases/9
header X-Tenant-Id=B
SQL uses tenant from header

21.1 Broken Invariant

Tenant context can be selected independently from authenticated identity.

In multi-tenant systems, tenant is part of authentication context, not just authorization filter.

21.2 Tenant Invariant

effectiveTenant must be derived from a verified tenant membership or verified tenant-scoped token, not from public request input alone

Implementation model:

public record AuthenticatedTenantContext(
        String tenantId,
        TenantBindingSource source,
        String subjectId,
        java.time.Instant verifiedAt
) {}

enum TenantBindingSource {
    TOKEN_CLAIM,
    SESSION_SELECTION,
    VERIFIED_MEMBERSHIP_LOOKUP,
    MTLS_CERT_SAN,
    SERVICE_ACCOUNT_BINDING
}

Failure rule:

if pathTenant != authenticatedTenant and no explicit tenant-switch protocol exists -> reject

22. Threat: Recovery Channel Abuse

Password reset and account recovery are often weaker than login.

Common attacks:

  • reset token leaked in logs,
  • token not one-time-use,
  • token stored plaintext,
  • reset does not revoke existing sessions,
  • email account compromise gives full takeover,
  • support agent resets MFA without strong proof,
  • phone OTP recovery vulnerable to SIM swap,
  • backup codes stored plaintext,
  • recovery flow reveals account existence.

22.1 Broken Invariant

Recovery proves the same level of identity as login.

Often false.

22.2 Recovery Rules

RuleWhy
Reset tokens are random, high entropy, short-lived, hashed at rest.DB/log leak should not enable reset.
Reset tokens are one-time-use.Prevent replay.
Reset success rotates credential version.Existing tokens/sessions can be invalidated.
MFA reset requires stronger process than password reset.MFA reset is account takeover path.
Recovery events are high-signal audit events.Needed for investigation.
User notified after recovery change.Early detection.

23. Threat: OAuth/OIDC Callback Attack

OIDC login adds specific threats:

  • missing state allows CSRF login,
  • missing nonce allows ID token replay/substitution,
  • weak redirect URI validation,
  • authorization code interception,
  • code replay,
  • mix-up attacks between providers,
  • trusting user email without issuer/account binding,
  • account linking takeover.

23.1 Broken Invariant

A successful callback means this browser initiated this login for this client and this provider.

Correct callback needs binding:

browser session + state + nonce + PKCE verifier + redirect_uri + client_id + issuer + code one-time-use

23.2 OIDC Callback State

public record OidcLoginTransaction(
        String transactionId,
        String stateHash,
        String nonceHash,
        String pkceVerifierEncrypted,
        String expectedIssuer,
        String clientId,
        String redirectUri,
        java.time.Instant expiresAt,
        boolean consumed
) {}

Transaction must be consumed atomically. Callback replay should fail.


24. Threat: Internal Header Spoofing

A common enterprise pattern:

Gateway authenticates -> sends X-User-Id to internal services

This can work only if internal services cannot be reached by untrusted clients and gateway strips/recreates identity headers.

24.1 Broken Invariant

Presence of X-User-Id means gateway authenticated it.

Presence is not provenance.

24.2 Controls

  • reject identity headers at public edge,
  • gateway strips all incoming identity headers,
  • gateway recreates signed/internal headers,
  • service reachable only through trusted network/mTLS,
  • internal services verify gateway identity,
  • prefer token validation or signed context over raw headers for sensitive systems,
  • log provenance of identity context.

25. Threat: SecurityContext Leakage

Java applications often store authentication in thread-local context.

Spring Security uses SecurityContextHolder. Servlet request handling often maps one thread to one request, but async execution, thread pools, reactive streams, scheduled jobs, and CompletableFuture can break assumptions.

25.1 Broken Invariant

The current thread's security context belongs to the current request.

Thread pools reuse threads. If context is not cleared or propagated correctly, identity can leak.

25.2 Bad Pattern

CompletableFuture.runAsync(() -> {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    audit(auth.getName());
});

Depending on configuration, this may see no auth, stale auth, or wrong auth.

25.3 Safer Pattern

Capture explicit immutable actor context at boundary:

public record ActorContext(
        String subjectId,
        String tenantId,
        String sessionId,
        String correlationId,
        java.time.Instant authenticatedAt
) {}

Pass it explicitly to async work:

public void submitCaseReview(CaseId caseId, ActorContext actor) {
    reviewExecutor.submit(() -> reviewWorkflow.start(caseId, actor));
}

Rule:

SecurityContext is a request-local convenience, not a durable identity propagation mechanism

26. Threat: Logging and Observability Leakage

Authentication data frequently leaks through logs, traces, metrics labels, and exception messages.

Sensitive fields:

  • Authorization,
  • Cookie,
  • Set-Cookie,
  • password,
  • OTP,
  • reset token,
  • refresh token,
  • authorization code,
  • client_secret,
  • API key,
  • private key,
  • JWK private material,
  • full ID token/access token.

26.1 Broken Invariant

Logs are safe because only internal people can access them.

Logs are often replicated, indexed, exported, retained, and accessed by many systems.

26.2 Redaction Design

public interface SensitiveValueRedactor {
    String redactHeader(String name, String value);
    String redactJsonField(String fieldName, String value);
}

Rule:

redaction must happen before data leaves process boundary, not only in SIEM dashboard

27. Threat: Key Compromise

If token signing key, HMAC secret, mTLS CA, or private key leaks, attacker may mint valid-looking authentication evidence.

27.1 Broken Invariant

Tokens signed by our key are trustworthy.

If key is compromised, this invariant is false.

27.2 Key Compromise Runbook Questions

  • Which keys exist?
  • Which tokens/artifacts were signed with each key?
  • What is the kid?
  • Can old key be disabled immediately?
  • Do services cache JWK sets? For how long?
  • Can all sessions/token families be revoked?
  • Are refresh tokens tied to credential/session version?
  • Can we distinguish tokens minted before/after compromise?
  • Are logs sufficient to find suspicious minting?

Design invariant:

key rotation must be routine before it is an emergency

28. Threat: IdP Outage and Fail-Open Behavior

When identity provider or JWK endpoint is down, systems sometimes fail open accidentally.

Examples:

  • skip token validation when JWK fetch fails,
  • accept stale keys forever,
  • cache introspection success too long,
  • fallback admin password enabled,
  • bypass auth for health checks too broadly.

28.1 Broken Invariant

Availability failure should not become authentication bypass.

Correct posture:

fail closed for unknown authentication, degrade gracefully for already-established sessions according to explicit risk policy

For example:

  • existing server-side sessions may continue until idle/absolute timeout,
  • new OIDC login cannot start if IdP unavailable,
  • API token validation may use cached JWK for bounded duration,
  • introspection-only opaque token APIs may return 503 rather than accept unknown token.

29. Threat-to-Control Matrix

ThreatPreventDetectRecover
Credential stuffingrate limit, MFA, breached password checkfailure spikes, impossible velocityforce reset, step-up, notify
Password sprayingglobal/tenant detection, password policysame password fingerprint failurestenant attack mode
Account enumerationgeneric errors, timing defenseuser-not-found velocitytune endpoint responses
PhishingWebAuthn/passkeys, origin bindingnew device/locationrevoke sessions, reset credential
MFA fatiguenumber matching, challenge limitsrepeated push denieslock risk, require stronger factor
Replaynonce, one-time token, rotationreuse detectionrevoke family/session
Session fixationrotate session idduplicate session anomaliesinvalidate sessions
Token theftshort TTL, storage hygienetoken use from new contextrevoke token/session
JWT confusionstrict validatorsinvalid token reasonsrotate key/config
Header spoofingstrip/recreate, mTLSdirect service access logsblock path, redeploy gateway rule
Tenant confusiontenant invariantcross-tenant mismatchrevoke sessions, fix resolver
Key compromiseKMS/HSM, rotationunusual signing/key accessemergency rotate, revoke all

30. Review Checklist for Java Code

Use this in pull request review.

30.1 Login Endpoint

  • Does it return generic public errors?
  • Does it avoid user enumeration via timing?
  • Is rate limiting before expensive password hashing?
  • Are failure reasons logged internally but redacted externally?
  • Is session id rotated after login?
  • Are MFA/step-up decisions explicit?
  • Are password values redacted from logs and exceptions?

30.2 Token Validation

  • Is signature verified by a real library?
  • Are issuer and audience validated?
  • Are algorithm and key source pinned by server config?
  • Is token type checked?
  • Is tenant claim matched to request context?
  • Is expiry enforced with reasonable clock skew?
  • Are ID tokens forbidden as API access tokens?

30.3 OIDC Login

  • Is state generated, stored, and consumed?
  • Is nonce generated and verified?
  • Is PKCE used where appropriate?
  • Is redirect URI exact/pinned?
  • Is issuer bound to login transaction?
  • Is callback transaction one-time-use?
  • Is account linking protected by reauthentication?

30.4 Session

  • Is session id never accepted via URL?
  • Are cookie flags correct?
  • Are idle and absolute timeouts present?
  • Is logout server-side?
  • Is session inventory/revocation possible?
  • Are sessions invalidated after credential reset?

30.5 Async/Event

  • Is actor context explicit?
  • Is SecurityContextHolder avoided in background work?
  • Does event payload distinguish initiating actor and executing service?
  • Is tenant included and verified?

31. Mermaid Attack Tree: Account Takeover

Attack tree seperti ini berguna untuk security review karena memaksa tim melihat login sebagai satu ekosistem, bukan satu endpoint.


32. Mini Case Study: Bug yang Terlihat Aman

Desain:

- SPA login via OIDC
- API menerima JWT
- API mengecek signature dan exp
- tenant id dikirim dari frontend lewat X-Tenant-Id

Bug:

API does not check aud and does not bind token tenant to X-Tenant-Id

Attack:

  1. User tenant A login secara sah.
  2. User mendapatkan access token valid dari issuer.
  3. User mengirim request ke /tenant/B/cases dengan X-Tenant-Id: B.
  4. API menerima token karena signature valid.
  5. Repository query memakai X-Tenant-Id.
  6. Cross-tenant access terjadi.

Fix:

- API validates audience.
- Tenant context is derived from verified claim/session membership.
- Public tenant header is only a requested tenant, not effective tenant.
- path/header tenant must match authenticated tenant selection.
- repository receives AuthenticatedTenantContext, not raw string from request.

33. Mini Case Study: Refresh Token Reuse

Bad design:

refresh_token table stores token plaintext
refresh endpoint checks token exists and not expired
same refresh token can be used many times
logout only deletes current access token cache entry

Attack:

  1. Refresh token stolen from device.
  2. Victim and attacker both refresh successfully.
  3. System cannot tell theft occurred.
  4. Logout does not stop attacker because refresh token still valid.

Better design:

refresh token rotation + hashed token storage + token family + reuse detection

State:


34. Threat Model Template

Gunakan template ini sebelum mengimplementasikan pattern autentikasi baru.

# Authentication Threat Model

## Flow
- Entry point:
- Actor:
- Credential/evidence:
- Trust boundary:
- Authentication result:
- Session/token artifact:

## Assets
- Secrets:
- Tokens:
- Keys:
- Recovery artifacts:
- Audit evidence:

## Invariants
- Identity invariant:
- Tenant invariant:
- Freshness invariant:
- Revocation invariant:
- Context propagation invariant:

## Threats
- Credential theft:
- Replay:
- Enumeration:
- Phishing:
- Confused deputy:
- Token substitution:
- Session fixation/hijacking:
- Recovery abuse:
- Key compromise:

## Controls
- Prevention:
- Detection:
- Recovery:

## Tests
- Unit tests:
- Integration tests:
- Security regression tests:
- Chaos/incident drills:

35. Latihan

Latihan 1 — Review Endpoint Login

Ambil endpoint login yang pernah kamu buat. Jawab:

  • Apakah response membocorkan user existence?
  • Apakah timing berbeda untuk user tidak ditemukan?
  • Apakah rate limiting terjadi sebelum password hashing?
  • Apakah password bisa masuk log?
  • Apakah session id berubah setelah login?
  • Apakah audit reason cukup detail?

Latihan 2 — Review JWT Resource Server

Cari semua validasi token:

  • signature,
  • issuer,
  • audience,
  • expiry,
  • token type,
  • tenant,
  • client,
  • assurance,
  • revocation/credential version.

Tandai mana yang belum ada.

Latihan 3 — Buat Attack Tree

Buat attack tree untuk:

Admin account takeover in a multi-tenant case management platform

Minimal cabang:

  • password compromise,
  • IdP compromise,
  • support override,
  • session hijack,
  • token validation bug,
  • tenant confusion,
  • audit tampering.

36. Ringkasan

Threat model authentication bukan dokumen formalitas. Ia adalah alat untuk mengubah desain.

Jika satu hal yang harus diingat:

Authentication aman bukan karena credential valid, tetapi karena bukti identitas valid untuk boundary, tujuan, waktu, tenant, actor, dan assurance yang tepat — serta bisa dideteksi dan dicabut ketika asumsi itu rusak.

Part berikutnya akan memetakan Java authentication landscape: Servlet, Spring Security, Jakarta Security, Jakarta Authentication/JASPIC, JAAS, JAX-RS, MicroProfile JWT, identity provider, dan kapan masing-masing masuk akal dipakai.


References

Lesson Recap

You just completed lesson 03 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.