SSO (Single Sign-On) artinya login sekali di satu tempat, lalu bisa masuk ke banyak aplikasi tanpa memasukkan password lagi. SAML 2.0 adalah salah satu protokol untuk itu, berbasis XML. Bayangin resepsionis gedung perkantoran. Dia memeriksa KTP-mu sekali, lalu memberi surat pengantar bertanda tangan untuk kantor yang mau kamu datangi. Kantor itu gak memeriksa KTP lagi. Dia cukup memeriksa tiga hal di surat: tanda tangan resepsionisnya asli, surat itu memang ditujukan ke kantor ini, dan belum lewat jamnya.
🧪 Tebakan cepat: Pak Andi sudah masuk Portal Sekolah lewat SSO. Lima menit kemudian dia membuka Perpustakaan Digital, yang memakai penyedia login yang sama. Dia diminta password lagi atau gak? Dan Perpustakaan memakai surat yang sama dengan Portal atau gak? Tebak dulu, jawabannya ada di bagian "Aplikasi kedua".
Tiga pihak
- IdP (Identity Provider): tempat login, si resepsionis. Yayasan sekolah punya satu di
https://idp.yayasan.test, misalnya Microsoft Entra ID, Google Workspace, atau Keycloak. - SP (Service Provider): aplikasi yang mau dimasuki, si kantor. Di sini dua: Portal Sekolah dan Perpustakaan Digital.
- Assertion: surat pengantarnya. Dokumen XML dari IdP yang berisi siapa user-nya dan atributnya, ditandatangani IdP.
Sebelum dipakai, SP dan IdP bertukar metadata sekali: SP memberi tahu entity ID-nya (nama unik SP, https://portal.sekolah.test) dan ACS URL-nya (Assertion Consumer Service, alamat yang menerima assertion: https://portal.sekolah.test/saml/acs). IdP memberi alamat login-nya (https://idp.yayasan.test/sso) dan sertifikat yang dipakai untuk memeriksa tanda tangannya.
Alurnya, langkah demi langkah
Ini alur SP-initiated: dimulai dari aplikasinya. Jam di SAML selalu UTC, jadi 08.00 WIB ditulis 01:00. Persis seperti di Lab:
| # | Dari, ke | Yang terjadi |
|---|---|---|
| 1 | Browser ke Portal | GET /rapor tanpa session. Portal membuat AuthnRequest dan menjawab 302 ke https://idp.yayasan.test/sso?SAMLRequest=...&RelayState=%2Frapor |
| 2 | Browser ke IdP | IdP belum kenal Pak Andi, jadi menampilkan halaman login. Pak Andi memasukkan password (dan MFA). IdP menyimpan session-nya sendiri |
| 3 | IdP ke browser | IdP membuat assertion yang ditandatangani, lalu mengirim halaman berisi form yang otomatis di-POST ke ACS URL Portal |
| 4 | Browser ke Portal | POST /saml/acs membawa SAMLResponse. Portal memeriksa assertion-nya. Lolos: Portal membuat session lokal dan menjawab 302 ke /rapor |
AuthnRequest-nya kecil. Di URL, XML ini dikompres (DEFLATE) lalu di-base64:
<samlp:AuthnRequest ID="_req7f3a91c2" Version="2.0" IssueInstant="2026-10-09T01:00:00Z"
Destination="https://idp.yayasan.test/sso"
AssertionConsumerServiceURL="https://portal.sekolah.test/saml/acs">
<saml:Issuer>https://portal.sekolah.test</saml:Issuer>
</samlp:AuthnRequest>
ID: nomor permintaan. Portal menyimpannya di session, supaya nanti bisa mencocokkan jawabannya.Issuer: siapa yang meminta, yaitu entity ID Portal.Destination: ke mana, yaitu alamat login IdP.RelayState=%2Frapordi URL membawa halaman tujuan, supaya setelah login Pak Andi kembali ke/rapor.
Assertion dan yang dicek SP
Ini isi assertion yang tiba di ACS Portal (bagian tanda tangannya dipendekkan):
<saml:Assertion ID="_a91c55e0" IssueInstant="2026-10-09T01:00:12Z">
<saml:Issuer>https://idp.yayasan.test</saml:Issuer>
<ds:Signature><!-- RSA-SHA256, sertifikat 4B:1E:9C:07:A2:F3:5D:88 --></ds:Signature>
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
<saml:SubjectConfirmationData InResponseTo="_req7f3a91c2"
Recipient="https://portal.sekolah.test/saml/acs" NotOnOrAfter="2026-10-09T01:05:12Z"/>
</saml:Subject>
<saml:Conditions NotBefore="2026-10-09T00:59:42Z" NotOnOrAfter="2026-10-09T01:05:12Z">
<saml:AudienceRestriction><saml:Audience>https://portal.sekolah.test</saml:Audience></saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Attribute Name="role"><saml:AttributeValue>teacher</saml:AttributeValue></saml:Attribute>
<saml:Attribute Name="department"><saml:AttributeValue>Matematika</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
Sebelum percaya satu huruf pun, Portal menjalankan enam cek. Semuanya harus lolos:
| Cek | Dibandingkan dengan | Di assertion ini |
|---|---|---|
| Signature | sertifikat IdP dari metadata | ditandatangani sertifikat 4B:1E:..., cocok |
| Issuer | https://idp.yayasan.test |
cocok |
| Audience | entity ID Portal, https://portal.sekolah.test |
cocok |
| Recipient | ACS URL Portal | cocok |
| InResponseTo | ID AuthnRequest yang disimpan di session |
_req7f3a91c2, cocok |
| NotBefore, NotOnOrAfter | jam server Portal saat POST tiba | tiba 01:00:13, masih di dalam jendela 5 menit |
Lolos semua, jadi NameID ([email protected]) dicocokkan ke user di database Portal, atribut role dan department dibaca, lalu Portal login-kan Pak Andi dengan session-nya sendiri. Satu yang gagal, assertion ditolak dan gak ada session yang dibuat. Lab memperlihatkan tiga penyebab gagal yang biasa ditemui saat memasang SSO: jam server SP yang maju 6 menit, entity ID yang salah ketik di IdP (https://portal.sekolah.test/ dengan garis miring di akhir), dan IdP yang sudah memakai sertifikat baru sementara metadata di SP masih yang lama.
Pemeriksaan tanda tangan XML jangan ditulis sendiri. Pakai library: onelogin/php-saml di PHP (dipakai juga oleh paket SAML untuk Laravel), atau @node-saml/node-saml di Node.
Aplikasi kedua
Lima menit kemudian Pak Andi membuka Perpustakaan Digital. Perpustakaan juga gak punya session, jadi dia juga mengirim AuthnRequest ke IdP. Bedanya di langkah 2: IdP sudah punya session Pak Andi, jadi gak ada halaman login. IdP langsung membuat assertion baru dengan Audience https://perpus.sekolah.test dan mem-POST-nya ke ACS Perpustakaan. Jadi jawaban tebakannya: gak diminta password lagi, dan suratnya beda. Assertion untuk Portal ditolak di Perpustakaan, karena audience-nya bukan Perpustakaan.
Latihan: tiga function
Latihannya memakai object JavaScript sebagai pengganti XML, dengan nama aplikasi https://myapp.com dan IdP https://idp.corp.com/sso:
buildAuthRequest(spEntityId, idpUrl, requestId)mengembalikan{ type: "AuthnRequest", id: requestId, issuer: spEntityId, destination: idpUrl }. Sama dengan atributID,Issuer, danDestinationdi XML tadi.validateAssertion(assertion, spEntityId)membandingkanassertion.audiencedengan entity ID SP, yaitu cek Audience. Beda:{ valid: false, reason: "Wrong audience" }. Sama:{ valid: true, userId: assertion.subject }. Recipient, yang dibandingkan dengan URL ACS, gak ikut di latihan.extractRoles(assertion)mengembalikanassertion.attributes.roles, atau[]kalau gak ada.
SAML atau OIDC
OIDC (OpenID Connect) adalah lapisan login di atas OAuth 2.0, protokol yang dipakai "Login dengan Google" di lesson Social Login. Tujuannya sama dengan SAML: IdP membuktikan siapa user-nya ke aplikasi.
| SAML 2.0 | OIDC | |
|---|---|---|
| Terbit | 2005 | 2014 |
| Bukti identitas | assertion, XML yang ditandatangani | ID token, JWT (JSON yang ditandatangani) |
| Cara sampai ke aplikasi | lewat browser: redirect, lalu form POST ke ACS | redirect membawa code, lalu server aplikasi menukar code itu langsung ke IdP |
| Cocok untuk | integrasi enterprise dan B2B, terutama sistem lama | aplikasi web dan mobile baru |
Aturan praktisnya: kalau pelanggan perusahaan meminta "harus bisa login pakai SAML dari IdP kami", sediakan SAML. Kalau kamu membangun dari nol, pilih OIDC: JSON lebih ringan, dan library-nya lebih banyak di web dan mobile.
⚠️ Jebakan yang sering ketemu
- Cuma membaca
NameID, lupa cek lainnya. Assertion yang ditandatangani untuk aplikasi lain, atau yang sudah lewat jamnya, tetap harus ditolak. Keenam cek wajib. - Jam server melenceng. Jendela waktunya cuma beberapa menit. Pasang NTP di server SP, dan beri toleransi kecil (puluhan detik), bukan menit.
- Sertifikat IdP diganti tanpa memperbarui metadata SP. Semua login gagal di cek signature. Impor metadata baru sebelum IdP berganti sertifikat.
- Assertion yang sama diterima dua kali. Simpan
IDassertion yang sudah dipakai sampaiNotOnOrAfter-nya lewat, dan tolak yang kedua.
TL;DR: SSO: login sekali di IdP, masuk ke banyak SP. Alur SP-initiated: SP mengirim AuthnRequest (menyimpan ID-nya), IdP me-login-kan user, IdP mem-POST assertion XML yang ditandatangani ke ACS URL, lalu SP memeriksa signature, issuer, audience (entity ID-nya sendiri), recipient (ACS URL-nya), InResponseTo, dan jendela waktu NotBefore/NotOnOrAfter sebelum membuat session sendiri. Aplikasi kedua gak minta password lagi karena IdP masih punya session, tapi tetap dapat assertion-nya sendiri. SAML (XML, 2005) untuk enterprise dan sistem lama, OIDC (JWT, di atas OAuth 2.0) untuk aplikasi baru.