Series MapLesson 06 / 35
Start HereOrdered learning track

Learn Java Identity Authentication Authorization Api Platform Part 006 Session Management Browser Api

19 min read3756 words
PrevNext
Lesson 0635 lesson track0106 Start Here

title: Learn Java Identity, Authentication & Authorization for Secure Enterprise API Platform - Part 006 description: Session management untuk browser, BFF, SPA, dan API platform: cookie security, CSRF, session fixation, logout, revocation, distributed sessions, token-backed sessions, dan Spring Security implementation. series: learn-java-identity-authentication-authorization-api-platform seriesTitle: Learn Java Identity, Authentication & Authorization for Secure Enterprise API Platform order: 6 partTitle: Session Management for Browser, BFF, SPA, and API Platforms tags:

  • java
  • identity
  • session-management
  • csrf
  • cookies
  • spring-security
  • bff
  • api-security date: 2026-06-28

Part 006 — Session Management for Browser, BFF, SPA, and API Platforms

Authentication membuktikan identity pada satu waktu.

Session management menjawab pertanyaan berikut:

Setelah user berhasil diautentikasi, bagaimana sistem mempertahankan state kepercayaan itu antar request tanpa membuka celah fixation, hijacking, replay, CSRF, stale privilege, tenant confusion, atau logout yang palsu?

Banyak sistem enterprise gagal bukan karena login-nya lemah, tetapi karena session-nya tidak punya lifecycle yang jelas.

Part ini membahas session management untuk:

  • browser-rendered web application,
  • Backend-for-Frontend / BFF,
  • SPA,
  • API resource server,
  • enterprise distributed session,
  • Spring Security.

1. Kaufman Objective

Target subskill:

Kamu mampu mendesain session architecture untuk Java enterprise platform yang aman untuk browser dan API, memahami kapan memakai cookie session, kapan memakai token, bagaimana CSRF bekerja, bagaimana logout/revocation harus dirancang, dan bagaimana menguji session lifecycle secara negatif.

Skill yang harus dikuasai:

SubskillTarget kemampuan
Session mental modelMembedakan authentication event, session, access token, refresh token, remember-device, dan step-up context.
Cookie securityMenggunakan HttpOnly, Secure, SameSite, path/domain, expiry, prefix, dan partitioning secara tepat.
CSRF modelTahu kapan CSRF relevan dan kapan tidak. Tidak mencampur CSRF dengan CORS.
Session lifecycleCreate, rotate, idle timeout, absolute timeout, revoke, logout, concurrent session, fixation defense.
BFF vs SPAMemilih token storage dan browser boundary yang aman.
Distributed sessionRedis/Spring Session trade-off, invalidation, consistency, failover.
Spring SecurityMengkonfigurasi session, CSRF, logout, fixation protection, dan security context repository.
TestingMenguji fixation, CSRF, stale session, logout, rotation, dan tenant switch.

2. Authentication Event vs Session vs Token

Jangan mencampur konsep berikut.

KonsepMaknaLifetimePemilik utama
Authentication eventBukti bahwa subject berhasil diautentikasi pada waktu tertentu.Historical evidence.Auth service/audit.
SessionServer/client state yang mempertahankan login antar request.Idle + absolute timeout.Application/BFF.
Access tokenArtifact untuk mengakses API resource.Pendek.Authorization server/resource server.
Refresh tokenArtifact untuk memperoleh access token baru.Lebih panjang, harus dilindungi.Authorization server/client.
Step-up contextEvidence sementara untuk high-risk action.Sangat pendek/purpose-bound.App/auth service.
Remember-deviceSinyal untuk menurunkan friction, bukan proof penuh.Terbatas dan revocable.App/auth service.

Rule sederhana:

Session adalah state aplikasi. Token adalah artifact protokol. Authentication event adalah evidence. Jangan jadikan satu object untuk semua.


3. Session Architecture Choices

Ada tiga desain umum.

3.1 Stateful server-side session

Browser menyimpan session id di cookie. Server menyimpan session state.

Kelebihan:

  • mudah revoke,
  • session state bisa server-side,
  • cookie bisa HttpOnly,
  • cocok untuk BFF/server-rendered app,
  • tidak perlu expose access token ke browser JavaScript.

Kekurangan:

  • butuh session store,
  • horizontal scaling perlu sticky session atau distributed session,
  • perlu CSRF protection untuk browser requests,
  • store outage dapat memengaruhi login.

3.2 Stateless token in browser

Browser menyimpan access token/refresh token, biasanya di memory/local storage/cookie.

Kelebihan:

  • scalable secara teknis,
  • cocok untuk native/mobile/API clients,
  • resource server bisa memvalidasi JWT tanpa session lookup.

Kekurangan untuk browser:

  • localStorage rentan XSS token theft,
  • cookie token membawa CSRF consideration,
  • revocation sulit jika token self-contained,
  • refresh token di browser adalah high-value target,
  • logout sering hanya client-side illusion.

3.3 BFF hybrid

Browser hanya punya secure cookie ke BFF. BFF menyimpan token server-side dan memanggil API downstream.

Kelebihan:

  • token tidak tersedia untuk browser JavaScript,
  • cocok untuk SPA dengan security requirement tinggi,
  • BFF bisa menerapkan CSRF, step-up, tenant context, dan audit,
  • downstream API menerima access token dari trusted confidential client.

Kekurangan:

  • tambahan layer,
  • perlu scaling BFF/session store,
  • perlu desain CORS/CSRF/cookie dengan benar,
  • BFF bisa menjadi high-value component.

Rekomendasi praktis untuk enterprise browser app:

Untuk browser-based enterprise app, default yang lebih defensible adalah BFF + HttpOnly Secure SameSite cookie + server-side token/session storage. Jangan expose refresh token ke browser kecuali threat model dan mitigasinya sangat jelas.


Cookie session harus opaque, random, high entropy, dan tidak memuat data sensitif.

AttributeFungsiRekomendasi
HttpOnlyMencegah akses dari JavaScript.Aktifkan untuk session cookie.
SecureCookie hanya dikirim via HTTPS.Wajib di production.
SameSiteMengontrol pengiriman cookie cross-site.Lax atau Strict; None hanya jika perlu cross-site dan harus Secure.
PathScope path cookie.Sesempit mungkin.
DomainScope domain/subdomain.Hindari domain luas jika tidak perlu.
Max-Age / ExpiresPersistence.Session cookie atau TTL eksplisit sesuai risk.
__Host- prefixMembatasi cookie agar host-only, secure, path /, tanpa Domain.Sangat baik untuk session utama.

Contoh header:

Set-Cookie: __Host-session=opaqueRandomValue; Path=/; Secure; HttpOnly; SameSite=Lax

Untuk cross-site embedding atau third-party context, SameSite=None; Secure mungkin diperlukan. Namun itu menaikkan kompleksitas CSRF dan browser privacy behavior.

Jangan simpan ini di cookie:

{
  "userId": "123",
  "roles": ["ADMIN"],
  "tenantId": "acme"
}

Lebih baik:

__Host-session=3Ge8gkP9kQjM...opaque random id...

Server-side session store:

public record SessionState(
        SessionId sessionId,
        SubjectId subjectId,
        AccountId accountId,
        TenantId tenantId,
        AuthenticationAssuranceLevel aal,
        AuthenticationMethod method,
        Instant authenticatedAt,
        Instant issuedAt,
        Instant lastSeenAt,
        Instant absoluteExpiresAt,
        Instant idleExpiresAt,
        Set<String> flags
) {}

5. Session Lifecycle

Session harus punya lifecycle eksplisit.

5.1 Creation

Session creation hanya terjadi setelah authentication final.

For multi-factor login:

  • password success menghasilkan pending authentication state,
  • MFA success menghasilkan authenticated session,
  • pending state punya TTL pendek,
  • pending state tidak boleh mengakses resource protected.

5.2 Rotation

Session ID harus dirotasi setelah authentication success untuk mencegah session fixation.

Session fixation terjadi ketika attacker membuat/mengetahui session ID lebih dulu, lalu korban login menggunakan session ID itu.

Mitigasi:

  • rotate session ID on login,
  • invalidate pre-auth session,
  • jangan menerima session ID dari URL,
  • set cookie secure attributes,
  • regenerate CSRF token setelah login.

5.3 Idle timeout dan absolute timeout

Gunakan dua timeout:

TimeoutMakna
Idle timeoutSession habis jika tidak ada aktivitas.
Absolute timeoutSession habis walaupun aktif terus.

Contoh:

  • normal employee app: idle 30 menit, absolute 8-12 jam,
  • admin console: idle 10-15 menit, absolute 4 jam,
  • high-risk step-up: 5-15 menit,
  • public low-risk portal: lebih longgar sesuai policy.

Jangan hanya memakai idle timeout. Session yang aktif terus bisa hidup terlalu lama.

5.4 Revocation

Session harus bisa dicabut karena:

  • logout,
  • password reset,
  • MFA reset,
  • account disabled,
  • tenant membership revoked,
  • suspected compromise,
  • admin/security action,
  • refresh token compromise,
  • device lost.

Revocation harus server-side bila ingin efektif. Self-contained JWT yang masih valid sampai expiry tidak bisa benar-benar dicabut tanpa introspection, denylist, short TTL, atau reference token.


6. CSRF Mental Model

CSRF terjadi ketika browser otomatis mengirim credential seperti cookie ke server saat user mengunjungi situs attacker.

CSRF bukan tentang attacker membaca response. CSRF tentang attacker membuat browser korban mengirim request authenticated.

CSRF relevan ketika:

  • authentication memakai cookies atau browser otomatis mengirim credential,
  • request memiliki side effect,
  • attacker bisa membuat browser mengirim request.

CSRF kurang relevan untuk:

  • API yang hanya menerima Authorization: Bearer header yang harus diset JavaScript attacker, selama CORS tidak mengizinkan attacker origin,
  • non-browser clients,
  • same-origin-only private backend.

Namun jangan menyimpulkan “API tidak perlu CSRF” tanpa melihat credential transport-nya.

Jika API menerima session cookie dari browser, CSRF relevan.


7. CSRF Defenses

7.1 Synchronizer token pattern

Server menyimpan CSRF token di session. Form/request harus mengirim token yang cocok.

Cocok untuk server-rendered atau BFF.

Token disimpan di cookie dan dikirim juga di header/body. Server memverifikasi keduanya cocok, sebaiknya signed/bound to session.

Cocok untuk SPA/BFF tertentu.

SameSite=Lax membantu mengurangi CSRF untuk banyak navigasi cross-site, tetapi bukan pengganti penuh untuk token pada semua sensitive state-changing request.

SameSite=Strict lebih ketat tetapi bisa mengganggu UX.

SameSite=None diperlukan untuk cross-site use case tertentu, tetapi harus Secure dan perlu CSRF defense kuat.

7.4 Origin/Referer validation

Untuk state-changing request, validasi Origin atau Referer dapat menjadi defense tambahan.

Caution:

  • jangan jadikan satu-satunya defense jika environment/proxy sering menghapus header,
  • handle absence dengan policy jelas,
  • allowlist harus ketat.

7.5 Custom header

Browser form biasa tidak bisa mengirim custom header tanpa CORS preflight. SPA dapat mengirim X-CSRF-TOKEN jika same-origin atau CORS diatur.


8. CORS Bukan CSRF

CORS mengontrol apakah browser mengizinkan JavaScript dari origin lain membaca response atau mengirim request tertentu dengan permission tertentu.

CSRF mengontrol apakah request authenticated dengan side effect boleh diterima.

Kesalahan umum:

We enabled CORS correctly, so CSRF is handled.

Ini salah.

Jika cookie otomatis dikirim, attacker bisa memicu form POST sederhana tanpa perlu membaca response.

Aturan:

  • CORS adalah browser read/access control antar origin.
  • CSRF adalah request integrity control untuk browser credential otomatis.
  • Keduanya berbeda dan bisa sama-sama diperlukan.

9. Spring Security Session Management

Spring Security menyediakan default yang kuat, tetapi harus disesuaikan dengan arsitektur.

9.1 Basic security filter chain untuk browser app

@Configuration
@EnableWebSecurity
class BrowserSecurityConfig {

    @Bean
    SecurityFilterChain browserSecurity(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/login", "/assets/**", "/error").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .deleteCookies("__Host-session", "JSESSIONID")
            )
            .sessionManagement(session -> session
                .sessionFixation(fixation -> fixation.migrateSession())
                .maximumSessions(3)
            )
            .csrf(csrf -> csrf
                // default enabled for browser app
            );

        return http.build();
    }
}

Catatan:

  • Spring Security mengaktifkan CSRF protection secara default untuk banyak konfigurasi servlet web.
  • Jangan disable CSRF hanya karena request memakai JSON.
  • Disable CSRF hanya jika endpoint tidak memakai browser cookie credential atau ada defense lain yang jelas.

application.yml:

server:
  servlet:
    session:
      cookie:
        name: __Host-session
        http-only: true
        secure: true
        same-site: lax
        path: /
      timeout: 30m

Untuk __Host- prefix:

  • cookie harus Secure,
  • path harus /,
  • tidak boleh ada Domain attribute.

Pastikan reverse proxy mengirim header yang benar agar aplikasi tahu request aslinya HTTPS.

9.3 SecurityContextRepository

Spring menyimpan SecurityContext dalam session untuk stateful browser app.

Untuk stateless API resource server, gunakan strategi berbeda.

@Configuration
class ApiSecurityConfig {

    @Bean
    SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt());

        return http.build();
    }
}

Disable CSRF di sini masuk akal jika API hanya menerima bearer token di Authorization header dan bukan cookie otomatis dari browser.


10. BFF Session Pattern

BFF pattern cocok untuk enterprise SPA.

Browser:

  • menyimpan hanya HttpOnly session cookie,
  • tidak menyimpan access token di localStorage,
  • memanggil BFF same-origin.

BFF:

  • menyimpan session state server-side,
  • menyimpan access/refresh token server-side,
  • menambahkan bearer token saat memanggil API,
  • menerapkan CSRF,
  • menerapkan step-up dan tenant context,
  • menulis audit event.

10.1 BFF security invariants

  • Browser never sees refresh token.
  • Browser JavaScript cannot read session cookie.
  • CSRF token required for unsafe methods.
  • BFF validates tenant context before downstream call.
  • BFF does not blindly forward arbitrary headers.
  • BFF logs correlation ID and subject context.
  • BFF enforces logout by revoking server-side session and tokens where possible.

10.2 BFF anti-pattern

Bad:

SPA stores refresh token in localStorage; BFF only proxies requests.

This is not really BFF. It is a proxy plus exposed browser token.

Better:

BFF owns OAuth client and token storage. Browser owns only secure same-site session cookie.

11. SPA Token Storage Trade-Off

If you build pure SPA without BFF, token storage becomes hard.

StorageRisk
localStoragePersistent and readable by JavaScript; XSS can steal token.
sessionStorageStill readable by JavaScript; shorter lifetime.
memory onlyBetter against persistent theft, but refresh UX harder.
HttpOnly cookieJS cannot read token, but CSRF matters if cookie authenticates API.

Pure SPA with browser-held token can be acceptable for some risk profiles, but for high-value enterprise/regulatory platforms, BFF is usually easier to defend.

Key point:

XSS and CSRF are not interchangeable. localStorage reduces CSRF exposure but increases XSS token theft risk. HttpOnly cookie reduces token theft by JS but requires CSRF controls.


12. Distributed Sessions

Java enterprise systems are usually horizontally scaled.

Options:

OptionProsCons
Sticky sessionSimple.Node failure kills session; poor elasticity; uneven load.
Database sessionDurable.Latency, write load, cleanup complexity.
Redis/session cacheFast, common.Cache outage impact, consistency, eviction risk.
Stateless tokenNo session store.Revocation/stale state harder.

Spring Session can externalize HTTP session to Redis/JDBC.

But do not treat distributed session as only infrastructure. It changes security properties:

  • revocation latency,
  • consistency across nodes,
  • session fixation handling,
  • session serialization safety,
  • failover behavior,
  • cleanup semantics,
  • session event audit.

12.1 Session data minimization

Do not store huge user profile/permissions in session.

Store minimal context:

public record MinimalSessionContext(
        SubjectId subjectId,
        AccountId accountId,
        TenantId tenantId,
        AuthenticationAssuranceLevel aal,
        AuthenticationMethod method,
        Instant authenticatedAt,
        Instant stepUpExpiresAt,
        long authzVersion
) implements Serializable {}

authzVersion can help detect stale entitlement state.

Example:

  • user role changes,
  • increment account/tenant authorization version,
  • session with old version must refresh authorization context or be revoked.

13. Logout Semantics

Logout is not one thing.

Logout typeMeaning
Local app logoutInvalidate app session only.
BFF logoutInvalidate app session and server-side token references.
Authorization server logoutEnd IdP/AS session if supported.
Federated logoutAttempt logout across identity providers/relying parties.
Back-channel logoutServer-to-server logout notification.
Global revokeSecurity action revoking all sessions/tokens.

User expectation often says “log me out everywhere”. Implementation may only do local logout unless explicitly designed.

13.1 Local logout checklist

  • invalidate server session,
  • delete session cookie,
  • rotate CSRF token,
  • clear remember-device if requested,
  • revoke refresh token if BFF owns one,
  • audit logout event,
  • redirect safely.

13.2 Security logout / revoke all

For password reset or compromise:

  • revoke all active sessions for account,
  • revoke refresh tokens,
  • invalidate remember-device tokens,
  • require MFA/passkey re-auth,
  • generate audit event,
  • notify user/admin.

13.3 Logout illusion with JWT

If browser deletes JWT locally, but token remains valid until expiry, server has not logged user out.

Mitigations:

  • short access token lifetime,
  • refresh token revocation,
  • introspection/reference tokens,
  • token denylist for emergency,
  • event-driven revocation in resource servers,
  • BFF server-side token storage.

14. Remember Me vs Remember Device

“Remember me” is ambiguous.

Possible meanings:

  1. keep me logged in longer,
  2. remember my username,
  3. skip MFA on this device,
  4. trust this browser for lower friction,
  5. persistent refresh session.

Do not implement without defining meaning.

14.1 Safer remember-device design

  • Remember-device token is not authentication by itself.
  • Token is random, high entropy, HttpOnly/Secure cookie if browser-based.
  • Server stores hash and metadata.
  • Token has TTL.
  • Token is invalidated after password/MFA change.
  • Token is bound to account/tenant and coarse device context.
  • High-risk action still requires step-up.
public record RememberDeviceToken(
        TokenId id,
        AccountId accountId,
        TenantId tenantId,
        String tokenHash,
        Instant issuedAt,
        Instant expiresAt,
        Instant lastUsedAt,
        boolean revoked,
        String deviceLabel
) {}

15. Tenant Switching and Session Context

Multi-tenant apps often allow a user to belong to multiple tenants.

Bad pattern:

session.setAttribute("tenantId", request.getParameter("tenantId"));

Better:

  • tenant switch is explicit action,
  • verify membership in target tenant,
  • update session context server-side,
  • clear tenant-scoped caches,
  • optionally require step-up for privileged tenant,
  • audit tenant switch.
public TenantSwitchResult switchTenant(
        AuthenticatedPrincipal principal,
        TenantId targetTenant,
        SessionId sessionId
) {
    if (!membershipService.isMember(principal.subjectId(), targetTenant)) {
        audit.tenantSwitchDenied(principal.subjectId(), targetTenant);
        return TenantSwitchResult.denied();
    }

    sessionStore.updateTenant(sessionId, targetTenant);
    authorizationCache.evict(sessionId);
    audit.tenantSwitched(principal.subjectId(), targetTenant);

    return TenantSwitchResult.allowed();
}

Failure mode:

  • session says tenant A,
  • token says tenant B,
  • request path says tenant C,
  • database query uses tenant D.

Mitigation:

Define exactly one authoritative tenant context per request, validate all other tenant hints against it, and fail closed on mismatch.


16. Session and Authorization Staleness

Session can outlive authorization state.

Examples:

  • user removed from role,
  • tenant membership revoked,
  • account disabled,
  • policy changed,
  • high-risk permission removed,
  • admin session remains active after demotion.

Mitigation patterns:

16.1 Short session lifetime

Simple but can hurt UX.

16.2 Authorization version

Store version in session and compare with current account/tenant version.

public boolean isAuthorizationContextFresh(SessionState session) {
    long currentVersion = authorizationVersionService.currentVersion(
            session.accountId(),
            session.tenantId()
    );

    return session.authzVersion() == currentVersion;
}

16.3 Event-driven session revocation

When role/account changes:

  • publish EntitlementChanged,
  • session service revokes affected sessions,
  • resource servers refresh cache,
  • audit event links entitlement change to session revocation.

16.4 Decision-time authorization

Do not rely entirely on session authorities for object-level authorization.

Object-level authorization must check current resource ownership/tenant/policy at decision time.


17. API Resource Server Session Policy

Resource servers should usually be stateless with respect to browser sessions.

They validate:

  • access token signature/introspection,
  • issuer,
  • audience,
  • expiry,
  • scopes/claims,
  • tenant context,
  • sender constraint if applicable,
  • authorization policy.

They should not create JSESSIONID accidentally.

Spring config:

@Bean
SecurityFilterChain resourceServer(HttpSecurity http) throws Exception {
    return http
        .securityMatcher("/api/**")
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .csrf(csrf -> csrf.disable())
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt
            .jwtAuthenticationConverter(jwtAuthenticationConverter())
        ))
        .authorizeHttpRequests(authz -> authz
            .anyRequest().authenticated()
        )
        .build();
}

Important:

  • This is for bearer-token API, not browser cookie API.
  • If the same API is called directly by browser with cookies, revisit CSRF.
  • Avoid mixing browser session endpoints and API token endpoints in one vague security chain.

18. Security Invariants

Use these invariants in review:

  1. Session ID is opaque, random, and high entropy.
  2. Session cookie is HttpOnly, Secure, and has explicit SameSite.
  3. Session ID is rotated after authentication.
  4. Pre-authentication state cannot access protected resources.
  5. CSRF protection exists for cookie-authenticated unsafe browser requests.
  6. Logout invalidates server-side session, not only client UI state.
  7. Password/MFA reset revokes or downgrades active sessions.
  8. Session has idle and absolute timeout.
  9. Step-up context is short-lived and purpose-bound.
  10. Tenant context is server-authoritative and mismatch fails closed.
  11. Authorization staleness has a mitigation.
  12. Browser JavaScript cannot read refresh tokens in high-value apps.
  13. Resource servers do not accidentally create stateful sessions.
  14. Session store outage behavior is defined.
  15. Session events are audit-visible.

19. Failure Modes

19.1 CSRF after moving from server-rendered UI to JSON API

Cause:

  • team disables CSRF because “we use JSON now”,
  • browser still sends session cookie,
  • attacker can submit form/simple request or exploit content type handling.

Mitigation:

  • CSRF decision based on credential transport, not response format,
  • require CSRF token for unsafe methods,
  • validate content type,
  • use SameSite and Origin checks as defense-in-depth.

19.2 Logout does not revoke refresh token

Cause:

  • app session deleted,
  • OAuth refresh token remains valid server-side,
  • background process recreates access token.

Mitigation:

  • BFF logout revokes refresh token,
  • clear token reference from session store,
  • audit revocation failure,
  • short refresh token rotation window.

19.3 Session survives role removal

Cause:

  • role copied to session at login,
  • no authz version check,
  • no revocation event.

Mitigation:

  • decision-time authorization,
  • authz version,
  • event-driven session revocation.

Cause:

Set-Cookie: session=...; Domain=.example.com; Path=/

Any compromised subdomain can become relevant to session threat model.

Mitigation:

  • host-only cookie,
  • __Host- prefix,
  • separate auth domains carefully,
  • subdomain isolation.

19.5 Session store eviction logs users out or corrupts state

Cause:

  • Redis memory pressure,
  • no eviction policy review,
  • session data too large.

Mitigation:

  • dedicated session store,
  • TTL aligned with session policy,
  • memory alerts,
  • minimal session payload,
  • graceful re-auth path.

20. Anti-Patterns

Anti-patternProblemBetter approach
Store JWT in localStorage for high-value browser appXSS steals token.BFF with HttpOnly cookie.
Disable CSRF for all JSON APIsCookie-authenticated JSON endpoints may still be vulnerable.Decide based on credential transport.
Session contains roles foreverAuthorization staleness.Store minimal context + decision-time authz/versioning.
Logout only clears frontend stateToken/session still valid.Server-side invalidation and token revocation.
One timeout onlySession can live too long.Idle + absolute timeout.
Tenant from request parameterTenant escape risk.Server-authoritative tenant context.
Remember-me as full authenticationLong-lived bypass.Remember-device as friction reducer only.
SameSite as only CSRF control for sensitive actionsBrowser behavior and edge cases vary.CSRF token + SameSite + Origin checks.
Resource server creates HTTP sessionState leakage and scaling surprise.SessionCreationPolicy.STATELESS.

21. Testing Strategy

21.1 Session fixation test

@Test
void loginRotatesSessionId() throws Exception {
    MvcResult beforeLogin = mvc.perform(get("/login"))
            .andReturn();

    MockHttpSession preAuthSession = (MockHttpSession) beforeLogin.getRequest().getSession(false);
    String oldId = preAuthSession.getId();

    MvcResult afterLogin = mvc.perform(post("/login")
                    .session(preAuthSession)
                    .param("username", "alice@example.com")
                    .param("password", "correct-password"))
            .andExpect(status().is3xxRedirection())
            .andReturn();

    MockHttpSession postAuthSession = (MockHttpSession) afterLogin.getRequest().getSession(false);
    assertThat(postAuthSession.getId()).isNotEqualTo(oldId);
}

21.2 CSRF test

@Test
void stateChangingRequestRequiresCsrfToken() throws Exception {
    mvc.perform(post("/settings/email")
                    .with(user("alice"))
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .param("email", "new@example.com"))
            .andExpect(status().isForbidden());
}

@Test
void stateChangingRequestWithCsrfTokenSucceeds() throws Exception {
    mvc.perform(post("/settings/email")
                    .with(user("alice"))
                    .with(csrf())
                    .param("email", "new@example.com"))
            .andExpect(status().is2xxSuccessful());
}

21.3 Logout invalidation test

@Test
void logoutInvalidatesSession() throws Exception {
    MockHttpSession session = loginAs("alice");

    mvc.perform(post("/logout")
                    .session(session)
                    .with(csrf()))
            .andExpect(status().is3xxRedirection());

    mvc.perform(get("/cases").session(session))
            .andExpect(status().is3xxRedirection());
}

21.4 Tenant switch test

@Test
void cannotSwitchToTenantWithoutMembership() {
    AuthenticatedPrincipal principal = principalInTenant("tenant-a");

    TenantSwitchResult result = tenantSessionService.switchTenant(
            principal,
            new TenantId("tenant-b"),
            currentSessionId()
    );

    assertThat(result.allowed()).isFalse();
    verify(audit).tenantSwitchDenied(principal.subjectId(), new TenantId("tenant-b"));
}

21.5 Authorization staleness test

@Test
void sessionWithStaleAuthorizationVersionMustRefreshOrFail() {
    SessionState session = sessionWithAuthzVersion(10L);
    when(authorizationVersionService.currentVersion(session.accountId(), session.tenantId()))
            .thenReturn(11L);

    assertThat(sessionFreshnessPolicy.isFresh(session)).isFalse();
}

22. Operational Checklist

  • Cookie session uses HttpOnly, Secure, explicit SameSite, and narrow scope.
  • Session ID rotates after login.
  • CSRF protection enabled for browser cookie unsafe requests.
  • CORS allowlist is explicit and does not rely on wildcard credentials.
  • Logout invalidates server-side session.
  • Refresh tokens are revoked or invalidated on logout/security events where applicable.
  • Password/MFA/recovery changes revoke or downgrade sessions.
  • Idle and absolute timeouts are configured by risk class.
  • Session store capacity, TTL, eviction, and failover are monitored.
  • Session payload is minimal and serializable safely.
  • Authorization staleness mitigation exists.
  • Tenant switch is explicit, validated, and audited.
  • Resource server endpoints are stateless unless intentionally stateful.
  • Session audit events exist for create, rotate, step-up, logout, revoke, expire, tenant switch.

23. Practice Drill

Desain session architecture untuk tiga client berikut:

  1. Internal regulator web app dengan React frontend.
  2. External company portal embedded under company-specific subdomain.
  3. Machine-to-machine API client.

Tentukan:

  • session/token mechanism,
  • cookie attributes,
  • CSRF strategy,
  • logout semantics,
  • session timeout,
  • step-up persistence,
  • tenant context handling,
  • revocation strategy,
  • tests.

Expected direction:

ClientRecommended design
Internal React appBFF + HttpOnly Secure SameSite cookie + CSRF token + server-side token storage.
External company portalBFF/server-side session, careful domain scoping, tenant-bound session, SameSite strategy based on embedding requirement.
M2M clientNo browser session. OAuth client credentials/workload identity, stateless resource server, token validation.

24. Summary

Session management adalah tempat authentication menjadi pengalaman nyata antar request.

Mental model utama:

Authentication proves identity. Session carries login state. Token carries protocol authorization. CSRF protects browser credential integrity. Logout/revocation ends trust. Authorization must still be evaluated per action/resource.

Desain terbaik bergantung pada client:

  • browser high-value app: BFF + secure cookie + CSRF,
  • resource API: stateless bearer/sender-constrained token,
  • machine-to-machine: no user session,
  • admin/high-risk: shorter lifetime + stronger step-up + aggressive revocation.

Di part berikutnya kita mulai masuk ke OAuth: delegation model, actors, clients, grants, scopes, authorization server, dan resource server.


References

Lesson Recap

You just completed lesson 06 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.