Authentication vs Authorization

12 min readFundamental
SecurityAuthenticationAuthorizationBackend

Pernahkah Anda melihat kode error 401 Unauthorized lalu bertanya-tanya, "Kenapa namanya Unauthorized padahal yang terjadi cuma belum login?" Selamat, Anda baru saja menabrak salah satu titik paling membingungkan di dunia backend. Jawaban singkatnya: namanya memang menyesatkan, karena 401 sebenarnya berkaitan dengan authentication, bukan authorization. 403 Forbidden-lah yang berbicara soal authorization.

Dua konsep ini terdengar mirip dan sering dipakai bergantian, tapi sebenarnya adalah dua tahap yang berbeda dalam setiap request yang butuh keamanan. Artikel ini akan mengupas perbedaannya secara sederhana, dengan contoh kode Spring Security dan kaitannya langsung dengan status code HTTP.

Dua Kata yang Sering Tertukar

Mari mulai dengan definisi singkat:

  • Authentication (autentikasi)membuktikan siapa Anda. Menjawab pertanyaan "kamu siapa?" dan "apakah kamu benar-benar orang itu?"
  • Authorization (otorisasi)menentukan apa yang boleh Anda lakukan. Menjawab pertanyaan "kamu punya izin untuk melakukan ini?"

Biar mudah, bayangkan dua analogi:

  1. KTP dan gedung kantor. Authentication adalah saat satpam memeriksa KTP Anda di pintu masuk — ia membuktikan bahwa nama yang Anda sebutkan benar-benar milik Anda. Authorization adalah setelah Anda masuk, ada area yang boleh Anda akses: ruang HR, ruang server, atau ruang direksi. Satpam tidak menghentikan Anda di ruang server karena ragu siapa Anda — Anda sudah terverifikasi. Ia menghentikan Anda karena level akses Anda tidak mencakup ruangan itu.

  2. Tiket konser. Authentication adalah ketika Anda menunjukkan tiket di pintu masuk — panitia memastikan tiket itu valid atas nama Anda. Authorization adalah ketika Anda ingin masuk area backstage dan ditolak: tiket Anda valid (authentication berhasil), tapi tiket kategori umum tidak memberi Anda hak masuk backstage (authorization gagal).

Kenapa pemula sering tertukar? Karena keduanya bekerja berurutan dan nyaris selalu hadir bersamaan. Ketika Anda "login", Anda mengalami keduanya dalam satu langkah: sistem memverifikasi password (authentication) lalu memuat peran/izin Anda (authorization). Perbedaannya baru terasa jelas saat salah satunya gagal — dan saat itulah 401 vs 403 muncul.

Diagram Alur Dua Tahap

Authentication vs Authorization

Perhatikan urutan di diagram di atas: authentication selalu lebih dulu, baru authorization. Mustahil menilai "apa yang boleh Anda lakukan" sebelum tahu "siapa Anda". Alur ini berlaku universal, dari aplikasi kecil sampai sistem enterprise:

  1. Klien mengirim request dengan membawa identitas (misal token di header Authorization).
  2. Server memverifikasi identitas tersebut — ini tahap authentication.
  3. Jika valid, server memeriksa hak akses terhadap resource — ini tahap authorization.
  4. Jika kedua tahap lolos, resource dikirim sebagai respons.

Aturan emas: authentication menjawab "siapa kamu?", authorization menjawab "jadi, kamu boleh melakukan apa?". Authentication dulu, authorization setelahnya.

Authentication: "Kamu Siapa?"

Authentication adalah proses membuktikan klaim identitas. Siapa pun bisa mengaku "saya Rina", tapi hanya Rina yang tahu password Rina. Bukti itulah yang disebut kredensial (credentials) — sesuatu yang klien serahkan sebagai bukti bahwa ia benar-benar pemilik identitas.

Bentuk Kredensial

Kredensial tidak melulu username + password. Ini beberapa bentuk yang umum di proyek backend:

Bentuk kredensialCara kerjaContoh penerapan
PasswordSesuatu yang hanya Anda ketahuiLogin klasik email + password
OTP (one-time password)Kode sekali pakai, dikirim/ter-generate singkatKode 6 digit via SMS, email, atau aplikasi authenticator
BiometricCiri fisik yang unikSidik jari, wajah (Face ID), retina
Social loginDelegasi ke penyedia pihak ketiga"Login dengan Google" lewat OAuth 2.0 / OpenID Connect
TokenBukti yang dikeluarkan server setelah login suksesJWT yang dikirim di header Authorization

Faktor Autentikasi (MFA)

Sistem modern jarang mengandalkan satu kredensial saja. Kerangka yang populer adalah tiga faktor, disingkat "kamu tahu / kamu punya / kamu adalah":

FaktorArtinyaContoh
Something you knowSesuatu yang Anda ketahuiPassword, PIN
Something you haveSesuatu yang Anda milikiPonsel, kartu fisik, kunci keamanan (hardware token)
Something you areCiri biologis AndaSidik jari, pemindaian wajah

Saat sebuah sistem mewajibkan dua faktor atau lebih dari kategori berbeda, itu disebut Multi-Factor Authentication (MFA). Contoh paling umum: password (you know) + kode OTP dari ponsel (you have). Kombinasi dua hal dari kategori yang sama — misal dua password berbeda — tidak dihitung sebagai MFA, karena keduanya sama-sama "kamu tahu".

Bagaimana Spring Security Menangani Authentication

Spring Security punya tiga komponen kunci yang bekerja berantai saat sebuah request login masuk:

KomponenPeran
AuthenticationManager"Kepala petugas keamanan" — gerbang utama yang memulai dan menyelesaikan proses verifikasi
AuthenticationProvider"Petugas pelaksana" — logika verifikasi sebenarnya, tahu cara mencocokkan kredensial
UserDetailsService"Arsip data user" — bertugas mengambil data user dari database berdasarkan username

Alurnya: request login dikirim → AuthenticationManager memanggil AuthenticationProvider yang relevan → provider meminta data user ke UserDetailsService → data dibandingkan dengan kredensial yang dikirim → jika cocok, objek Authentication dibuat dan disimpan di SecurityContext untuk request berikutnya. Referensi lengkapnya ada di dokumentasi resmi Spring Security untuk servlet authentication.

Kode: UserDetailsService + PasswordEncoder

Berikut contoh konfigurasi paling umum: mengambil user dari database dan menyimpan password dalam bentuk hash, bukan plaintext. Perhatikan bahwa kita tidak pernah menyimpan password asli.

@Service
public class AppUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public AppUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User tidak ditemukan: " + username));

        return org.springframework.security.core.userdetails.User
                .withUsername(user.getUsername())
                .password(user.getPassword()) // simpan hash BCrypt, bukan plaintext!
                .roles(user.getRoles().toArray(new String[0]))
                .build();
    }
}

Dan konfigurasi PasswordEncoder agar Spring Security tahu cara mencocokkan hash saat login:

@Configuration
public class SecurityBeansConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        // BCrypt meng-hash password + menambah salt otomatis
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

Catatan penting: UserDetailsService hanya membaca data user. Verifikasi password dilakukan oleh AuthenticationProvider lewat PasswordEncoder.matches(...). Prinsip ini membuat penggantian mekanisme verifikasi (misal ke LDAP) tidak mengubah cara data user disimpan.

Authorization: "Kamu Boleh Apa?"

Setelah identitas terbukti, pertanyaan berikutnya adalah: apa yang boleh dilakukan user ini? Inilah authorization. Spring Security membaginya dalam beberapa tingkatan yang bisa dikombinasikan:

Tingkatan Authorization

TingkatanDimana diaturContoh
URL-basedFilter chain di security configHanya user ber-role ADMIN yang boleh akses POST /api/users
Method-basedAnotasi di method controller/service@PreAuthorize("hasRole('ADMIN')") pada method hapus
Object-levelLogika bisnis di dalam methodUser hanya bisa mengedit data miliknya sendiri (owner.id == auth.id)

Pendekatan paling umum di proyek kecil-menengah adalah menggabungkan URL-based (untuk aturan global) dan method-based (untuk aturan spesifik per endpoint).

RBAC dan Permission/Scope

Ada dua gaya memodelkan hak akses yang perlu Anda kenal sejak awal:

  • RBAC (Role-Based Access Control) — user diberi role (misal USER, ADMIN), dan setiap role memetakan ke seperangkat aksi. Sederhana, mudah dikelola, cocok untuk sebagian besar aplikasi.
  • Permission / Scope — akses didefinisikan per aksi granular (misal user:read, user:write, report:delete), sering dipakai pada API publik dengan OAuth 2.0 dan dapat digabungkan dengan RBAC.

Analoginya: RBAC seperti "Anda pegawai, jadi boleh masuk gedung", sedangkan permission seperti "Anda boleh memasuki lantai 3 saja, tapi tidak lantai server". Kebanyakan sistem produksi memakai RBAC di atas dan menerjemahkan role menjadi daftar permission.

Kode: authorizeHttpRequests + @PreAuthorize

Berikut konfigurasi URL-based yang umum di Spring Security 6:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login", "/register", "/css/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/users/**").authenticated()
                .anyRequest().denyAll()
        );
        return http.build();
    }
}

Dan contoh method-based di controller, misalnya hanya admin yang boleh menghapus user:

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

Jangan lupa aktifkan method security:

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
    // kosong; anotasi @PreAuthorize kini aktif di seluruh aplikasi
}

Referensi lengkap untuk semua pola di atas ada di dokumentasi resmi Spring Security untuk servlet authorization.

401 vs 403: Dua Saudara yang Sering Disalahpahami

Ini bagian paling penting — dan paling sering keliru. Ketika sebuah request gagal, server menjawab dengan status code. Dua code berikut hampir selalu muncul bersama dalam konteks keamanan:

Status codeNamaArti sebenarnyaAuthentication atau Authorization?
401UnauthorizedBelum terautentikasi — server tidak tahu siapa Anda: token tidak ada, tidak valid, atau kedaluwarsaGagal authentication
403ForbiddenSudah terautentikasi tapi tidak punya hak atas resource tersebutGagal authorization

Perhatikan ironi namanya: 401 Unauthorized sebenarnya berarti tidak terautentikasi. Nama ini peninggalan sejarah dan sudah terlalu melekat untuk diubah — bahkan MDN mengakuinya sebagai nama yang menyesatkan.

Contoh skenario agar lebih jelas:

  1. Request tanpa token ke /api/admin/users401. Server: "Saya tidak kenal Anda, tunjukkan identitas dulu."
  2. Request dengan token valid milik user biasa (role USER) ke /api/admin/users403. Server: "Saya tahu Anda — Anda user123 — tapi Anda bukan admin, tidak boleh masuk."
  3. Request dengan token kedaluwarsa401. Server: "Token Anda dulunya valid, tapi sekarang sudah lewat masa berlakunya."

Cara mengingat cepat: 401 = who are you? (gagal autentikasi), 403 = who do you think you are? (gagal otorisasi). Yang pertama dijawab dengan login, yang kedua tidak bisa dijawab dengan login apa pun.

Header WWW-Authenticate

Satu detail kecil yang sering luput: spesifikasi 401 mengharuskan respons menyertakan header WWW-Authenticate yang memberi tahu klien skema autentikasi apa yang diharapkan — misal WWW-Authenticate: Bearer untuk token, atau WWW-Authenticate: Basic untuk username/password. Sedangkan 403 tidak mengharuskan header ini. Jadi jika sebuah endpoint melindungi resource dengan token dan Anda melihat 403 tanpa pernah diminta token, itu indikasi konfigurasi yang salah.

Alur Gabungan dalam Satu Request

Sekarang mari satukan semuanya. Berikut alur satu request DELETE /api/users/42 yang dikirim frontend:

  Klien (Frontend)                          Server Backend
        |                                         |
        |  1. DELETE /api/users/42                |
        |     Authorization: Bearer <JWT>         |
        |---------------------------------------->|
        |                             2. Filter chain
        |                                |---------| 
        |                                | a. Parse token JWT
        |                                | b. Verifikasi tanda tangan & masa berlaku
        |                                |    -> AUTHENTICATION (gagal = 401)
        |                                | c. Muat role user (mis. "ADMIN")
        |                                | d. Cocokkan role vs aturan endpoint
        |                                |    -> AUTHORIZATION (gagal = 403)
        |                                | e. Panggil service -> hapus dari DB
        |                                         |
        |  3. 204 No Content / 403 / 401          |
        |<----------------------------------------|

Tahap (a)–(b) adalah authentication; tahap (c)–(d) adalah authorization. Keduanya hidup dalam filter chain Spring Security — rangkaian filter yang berjalan sebelum request menyentuh controller Anda. Lihat visualisasinya pada diagram spring-security-filter-chain.svg jika Anda ingin mempelajarinya lebih dalam.

Praktik Umum di Proyek Backend

Di dunia nyata, pola yang hampir selalu Anda temui di proyek backend adalah:

  1. Login menghasilkan token (authentication). Endpoint POST /api/auth/login memverifikasi kredensial. Jika benar, server mengeluarkan token (biasanya JWT) yang berisi identitas dan role user.
  2. Token dipakai di setiap request. Klien mengirim token pada header Authorization: Bearer <token>. Server tidak perlu mengingat sesi di server (stateless) karena semua informasi sudah terbungkus dalam token.
  3. Otorisasi di filter chain / anotasi. Verifikasi role tidak ditulis manual di dalam controller, melainkan dideklarasikan di security config (authorizeHttpRequests) atau anotasi (@PreAuthorize). Controller tetap fokus pada logika bisnis.

Pola ini konsisten dengan yang kita bahas di Anatomi Protokol HTTP — khususnya pada tabel headers, di mana Authorization dijelaskan sebagai "kredensial/identitas klien".

Kesalahan Umum

Berikut jebakan yang paling sering saya lihat — termasuk dari diri saya sendiri:

KesalahanKenapa berbahayaPerbaikan
Menganggap 401 dan 403 samaKlien (atau Anda) salah menebak penyebab kegagalan, debugging jadi buang waktuIngat: 401 = siapa Anda, 403 = apa boleh Anda lakukan
Hanya mengecek "sudah login" tanpa cek roleSemua user yang login bisa mengakses resource adminGunakan .hasRole("ADMIN") atau @PreAuthorize
Hardcode cek role di dalam controllerAturan keamanan tersebar, mudah terlewat, sulit diauditTaruh di security config / anotasi, satu sumber kebenaran
Menyimpan password plaintextSatu bocornya database, semua akun terbukaSelalu hash dengan BCryptPasswordEncoder
Menghilangkan header WWW-Authenticate pada 401Menyalahi spesifikasi dan klien bingung skema auth apa yang dipakaiSertakan header sesuai skema (Bearer, Basic, dst.)

Satu contoh buruk yang sering muncul di code review:

// BURUK: logika otorisasi dibangun manual di controller
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id, Authentication auth) {
    boolean isAdmin = auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
    if (!isAdmin) {
        throw new AccessDeniedException("Tidak boleh!");
    }
    userService.deleteUser(id);
}

Versi yang benar cukup dengan deklarasi, dan aturannya tetap terpusat:

// BAIK: aturan dinyatakan sekali di config/anotasi
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(@PathVariable Long id) {
    userService.deleteUser(id);
}

Keuntungan pendekatan deklaratif: siapa pun yang membaca kode langsung tahu aturannya, dan jika kebijakan berubah, cukup diubah di satu tempat.

Ringkasan

Authentication dan authorization adalah dua tahap berurutan yang sering dianggap satu hal. Authentication membuktikan identitas ("kamu siapa?") lewat kredensial dan faktor-faktor MFA; di Spring Security ia ditangani oleh AuthenticationManager, AuthenticationProvider, dan UserDetailsService. Authorization menentukan hak akses ("kamu boleh apa?") lewat role dan permission; di Spring Security ia diatur dengan authorizeHttpRequests dan @PreAuthorize. Pembeda paling cepat untuk memeriksa pemahaman Anda adalah status code: 401 berarti gagal authentication, 403 berarti gagal authorization. Ingat juga urutannya — authentication selalu datang lebih dulu, karena Anda tidak bisa memberi izin kepada orang yang belum Anda kenal.

Lanjut membaca

← Back to technical articles