Memulai: Panduan Integrasi
Persyaratan Awal
Hal-hal yang Disiapkan oleh Pihak yang Mengandalkan atau Klien (RP)
Untuk mengintegrasikan SSO dengan INApas, klien harus menyiapkan hal-hal berikut ini:
1. Nama
Berikan nama entitas atau pemilik bisnis yang valid dan terdaftar.
2. Nama Aplikasi
Tentukan nama aplikasi yang akan digunakan sebagai client_id selama proses autentikasi.
3. Informasi Bisnis
Siapkan detail berikut untuk aplikasi:
-
Ikon atau Logo Aplikasi
Logo atau ikon aplikasi dengan ukuran minimum 256x256 piksel untuk identifikasi visual. -
Deskripsi Aplikasi Ringkasan singkat tentang fungsi dan tujuan aplikasi.
-
Tautan Situs Web Resmi atau App Store
Berikan tautan ke situs web resmi atau daftar aplikasi di Play Store/App Store. -
Tautan Persyaratan Layanan (ToS)
Sertakan tautan ke ketentuan layanan yang mengatur penggunaan aplikasi. -
Tautan Kebijakan Privasi
Cantumkan tautan ke kebijakan privasi yang menguraikan bagaimana data pengguna diproses dan dilindungi.
4. Informasi Klien Dasar
Berikan detail teknis berikut:
a. Redirect URL
-
Tentukan URL ke mana pengguna akan dialihkan setelah autentikasi.
Contoh:https://example.com/connect. -
Pastikan titik akhir ini aktif dan mampu memproses kode otorisasi atau token.
b. CORS Origin yang Diizinkan
- Tentukan domain atau asal klien untuk menghindari kesalahan lintas asal.
Contoh:example.com/*.
c. Audiens
- Tentukan identitas aplikasi atau layanan yang diakses melalui OpenID.
Contoh:INAKU.
d. Callback URL
- Tentukan titik akhir untuk menangani data panggilan balik dari penyedia OIDC.
Contoh:http://localhost:3000/callback.
5. Pembuatan Pasangan Kunci
Untuk mengamankan komunikasi antara aplikasi klien dan penyedia autentikasi, buatlah pasangan kunci menggunakan algoritme Eliptic Curves P-512. Metode ini memastikan implementasi proses kriptografi yang cepat dan aman.
- Kunci Publik: Digunakan oleh OpenID sebagai JWKS (JSON Web Key Set) untuk memverifikasi tanda tangan JWT (JSON Web Token) dan memungkinkan komunikasi yang aman.
- Kunci Pribadi: Disimpan dengan aman di server Anda dan digunakan untuk menandatangani permintaan. Ini memastikan keaslian dan integritas data yang dipertukarkan antara klien dan server.
Kami menggunakan:
ES512untuk menandatangani JSON Web Tokens (JWT) untuk memverifikasi identitas klien dan mengamankan pertukaran data.- JWE (JSON Web Encryption) menggunakan
ECDH-ES + A256KWuntuk mengenkripsi data sensitif, memastikan kerahasiaan dan transmisi informasi pengguna yang aman
Untuk contoh pembuatan kunci, lihat:
Contoh Pembuatan Kunci: GitHub - inadigital-inapas/kuncy.
6. Mengonfigurasi OpenID pada Server
Untuk menyelesaikan integrasi, kirimkan informasi berikut ini ke OpenID (INApas):
- Redirect URL
- Asal CORS yang Diizinkan
- Audiens
- JWKS (Kunci Publik)
- URL JWKS: Pastikan kunci publik dapat diakses melalui URL, misalnya,
https://example.com/jwks. - Cakupan**: Cantumkan cakupan yang diperlukan oleh aplikasi, seperti:
nama, telepon, DoB, PoB, Akses Offline, Offline, OpenID, Refresh, NIK.
Server OpenID akan memvalidasi dan mendaftarkan Aplikasi Anda sebagai Klien.
Pemeriksaan Autentifikasi
Tindakan ini dilakukan ketika mengklik tombol login dengan INA PAS.
Peningkatan Keamanan
-
PKCE → meningkatkan implementasi dengan code_challenge dan code_verifier
-
State → menghasilkan state acak dengan generator nomor acak yang aman
Jika URL callback Anda ditulis di NodeJS, disarankan untuk menggunakan node-forge atau crypto bawaan jika memungkinkan. Referensi → Pemanggilan balik URL Node.js.
Contoh sisi server menggunakan Node.js
import forge from 'node-forge'
// Generate random state
const hashed = forge.md.sha256.create();
hashed.update(forge.util.bytesToHex(forge.random.getBytesSync(16)));
const state = hashed.digest().toHex();
// Generate PKCE code verifier and code challenge
const codeVerifier = forge.util.bytesToHex(forge.random.getBytesSync(32));
const codeChallengeHash = forge.md.sha256.create();
codeChallengeHash.update(codeVerifier);
const codeChallenge = forge.util.encode64(codeChallengeHash.digest().bytes()).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
// Construct the authorization request URL
const redirection = new URL(`/sso/oauth2/auth`, `https://dev-inapass-api.govtechindonesia.id`);
redirection.searchParams.set(`client_id`, `<PUT YOUR CLIENT ID HERE>`);
redirection.searchParams.set(`redirect_uri`, `<PUT YOUR CALLBACK URL HERE>`);
redirection.searchParams.set(`response_type`, `code`);
redirection.searchParams.set(`state`, state);
redirection.searchParams.set(`code_challenge_method`, `S256`);
redirection.searchParams.set(`code_challenge`, codeChallenge);
// Store state and code verifier in secure cookies
Astro.cookies.set('oidc-state', state, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
});
Astro.cookies.set('oidc-code-verifier', codeVerifier, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
});
// Redirect to the authorization endpoint
return Astro.redirect(redirection.toString());
Pertukaran Token
Tindakan ini dilakukan setelah halaman persetujuan disetujui oleh halaman autentikasi INApas dan akan secara otomatis dialihkan dari mesin SSO.
Peningkatan Keamanan
-
Periksa apakah status yang ditetapkan pada saat login sudah valid
-
Pastikan code_challenge tidak dimanipulasi dengan menggunakan code_verifier
-
Gunakan jwt_assertion saat melakukan pertukaran token
-
Gunakan kunci pribadi yang dibuat sebelumnya untuk menandatangi data
-
Gunakan nilai-nilai yang benar untuk menghasilkan JWT (JSON Web Token) yang ditandatangani. Hal ini menjaga integritas dan keamanan data.
-
Gunakan KID (Key ID) sebagai bagian dari header JWT. KID ini menggunakan format hash
sha256dan berfungsi untuk mengidentifikasi kunci yang digunakan untuk menandatangani token.
-
-
KID adalah identifier unik yang disediakan oleh INApas untuk memudahkan sistem mengenali kunci tertentu dalam proses validasi. KID membantu mengarahkan sistem ke kunci yang benar tanpa perlu menyimpan semua informasi kunci dalam JWT..
{
'iss': '<YOUR CLIENT ID>',
'sub': '<YOUR CLIENT ID>',
'aud': '<PLACE THE TOKEN ISSUER URL>', // https://dev-inapass-api/sso/oauth2/token
'jti': '<PLACE UNIQUE RANDOM>',
'iat': '<DATE NOW>' // Math.floor(Date.now() / 1000)
}
Harap diperhatikan, private key harus disimpan secara pribadi di dalam KMS atau penyimpanan rahasia.
Contoh sisi server menggunakan Astro.js
import crypto from 'crypto';
import * as Jose from 'jose';
const query = await Astro.url.searchParams
const code = query.get("code")
const state = query.get("state")
const scope = query.get("scope")
const clientId = "<YOUR CLIENT ID HERE>"
const redirectUri = "<YOUR REDIRECT URL HERE>"
const stateCookie = Astro.cookies.get('oidc-state')
const codeVerifierCookie = Astro.cookies.get('oidc-code-verifier')
if (state !== stateCookie.value) {
console.error('Error!', stateCookie.value, state)
// return error here
}
if (codeVerifierCookie.value == "") {
console.error('Error! security tampered!')
// return error here
}
Astro.cookies.delete('oidc-state')
Astro.cookies.delete('oidc-code-verifier')
const privateKey = `-----BEGIN PRIVATE KEY-----
<PUT YOUR PRIVATE KEY FOR SIGNING HERE>
-----END PRIVATE KEY-----`;
const privatePKCS = await Jose.importPKCS8(privateKey, 'ES512')
const jwt = await new Jose.SignJWT({
iss: clientId,
sub: clientId,
aud: `<PUT SAME URL WITH TOKEN REQUEST>`,
exp: Math.floor(Date.now() / 1000) + 60, // Expires in 1 minutes
jti: crypto.randomUUID(),
iat: Math.floor(Date.now() / 1000)
})
.setProtectedHeader({ alg: 'ES512', kid: '<PUT KID VALUE FROM JWK>' })
.sign(privatePKCS);
const tokenResponse = await fetch(`https://dev-inapass-api.govtechindonesia.id/sso/oauth2/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri,
client_id: clientId,
code_verifier: codeVerifierCookie.value,
scope: "openid offline_access profile",
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: jwt,
})
});
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json();
console.error('Error exchanging authorization code:', errorData);
return new Response('Error exchanging authorization code', { status: 500 });
}
const tokens = await tokenResponse.json();
Astro.cookies.set('login-session', JSON.stringify(tokens), {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
})
// decrypting tokens JWE
const decryptPrivateKey = `-----BEGIN PRIVATE KEY-----
<PUT YOUR PRIVATE KEY FOR DECRYPTION HERE>
-----END PRIVATE KEY-----`;
const parsedToken = Jose.decodeJwt(tokens.id_token)
const encryptedData = parsedToken["encrypted_data"]
const privateKey = await Jose.importPKCS8(decryptPrivateKey, 'ECDH-ES+A256KW');
const { plaintext } = await Jose.compactDecrypt(encryptedData.toString(), privateKey);
const plaintextStr = new TextDecoder().decode(plaintext);
const payloadObj = JSON.parse(plaintextStr);
// print decrypted data
console.log(payloadObj)
return Astro.redirect("http://localhost:3020")
Token Hasil
Contoh token hasil yang dipertukarkan
{
access_token: 'INPSS.AT~g7_9T689V00YUAkBwCzf2wvEZNykCzxlCApayyXzQzQ.J5vJMXyKesz5mXxo3h8yciTmdO1b8RTReeFSX5sCSWY',
expires_in: 3599,
id_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyMTg2YmRlLTBiNmYtNDRlMS1hZjgxLTE0YjFkZGVjYjg3NSIsInR5cCI6IkpXVCJ9.eyJhY3IiOiJ1cm46bWFjZTppbmNvbW1vbjppYXA6c2lsdmVyIiwiYW1yIjpbInBpbiJdLCJhdF9oYXNoIjoiTlRqU0VWdlNLRVF4OHFON251Vko3ZyIsImF1ZCI6WyJpbnBzcy1ycC13aXRoLWp3a3MtaWQiXSwiYXV0aF90aW1lIjoxNzIxNjM2MzQ4LCJleHAiOjE3MjE2Mzk5NTAsImlhdCI6MTcyMTYzNjM1MCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo0NDQ0IiwianRpIjoiNTY1ZmZmNGMtNjAzZC00YmViLWFmNDktOTg0MzhjZWM2MmY5IiwicHJvZmlsZSI6eyJlbWFpbCI6ImZiZkBnbWFpbC5jb20iLCJuYW1lIjoiRnVsYW4gQmluIEZ1bGFuIiwibmlrIjoiMTAwMDIwMDAzMDAwNDAwMCJ9LCJyYXQiOjE3MjE2MzYzNDIsInNpZCI6IjMwYzZiYjNjLWE5ZmMtNGVhYy04ZTE1LTY4MDMzYmNjZDQ1MyIsInN1YiI6ImluYXBhc3NfaWQtIn0.2-iEuP0Ht8TvrX0MW0DhB3FpNO9LBJM4_OMvw8AbMs8VP2ALAfryqu58z9-ms5jg8H4dqCp2-DR3fQ_LumlX0KjphHo3iUi1UiI4aH_lZTrXLWLOqL14AeE2oTuke-1DS3vbkUzhwdqEMTJa0mXGMLHsXWl1o_m4sJxz41KRo0-zZ0HWeXNsRQF97TSqG4CfAm7tZZUk7mNuc_KKz1pzgdkoqXMVPASEqKVaR4zE8qQtrQQzSDXCowdCMeh8YxceUzMF905TTRLns19H17ymJsf_WeiadugheJuTmvtXAF8WoU_FhLDRGmeu3Vx6DEuOIGGG6hjjDiwwLzUORtNPYCWgwN4Jw_lrvetPbeDMBHOuDuifZ47KVDLwDg5_4ibaoI35xqc-5mJqP9Lbt5rcT3ICI0VjHfGaVk-oA72bmq-gSu4rs5_Rs8nC31jUmBfJClFXFrc6r6CnwZN2CUiycCAhsq6pteuMCnlQgBfgK2NlwBDkC_rMJ4aCL-wJGo3fKcAIJJQkB2nXM4C-ow1jeWET1Ji6cK5dl_cYgFEIem-pWBBS7ft2_JxDFZhftXdjzpBj-noLnPEAiGQzDecJNfVME6eZZ-xjy_bAde1LnZ1dA93t7JkdDZ5I2QBtbqOJfdE9ESYKKLPdrE9lmeTQzsyE4PKKN8WHOU1ve8YPyJ4',
refresh_token: 'INPSS.RT~pppBiuwpo7k63TlSRztcoujTqhiZfEtSQNxrBbwtVIk.f8-ht7lTo6vWh7M8i2F5CwqQUtAbo-TQJGOVXfYW_M4',
scope: 'openid offline_access profile email nik',
token_type: 'bearer'
}
Elemen Inti :
-
access_tokendigunakan untuk mengautentikasi panggilan API lainnya -
id_tokenberisi informasi tentang data pengguna dan cakupannya -
refresh_tokendigunakan untuk membuat ulang token akses yang telah kedaluwarsa
Token ID
Contoh data id_token yang diuraikan
{
acr: 'urn:mace:incommon:iap:silver',
amr: [ 'pin' ],
at_hash: 'mnsqlMl62E6hesMelJOQ3A',
aud: [ 'portalku-mania' ],
auth_time: 1733299450,
exp: 1733303052,
iat: 1733299452,
iss: 'http://localhost:9000/sso',
jti: '06675ec1-53e1-4b18-8435-67c896d4e63e',
profile: {
aal: 2,
dob: '01/01/1990',
email: 'jggm@emailku.com',
name: 'JAN GABE GHIYAST MUBARIZ',
nik: '1000200030004000',
phone: '62810002000'
},
rat: 1733299435,
sid: '4a6ec125-e537-4118-bbc7-27b0245a3ce4',
sub: 'JGJE6EE0GX'
}
Refresh Token
Jika access_token anda sudah kedaluwarsa, Anda bisa mendapatkan access_token baru dengan menukarkan refresh_token yang sudah diberikan.
Cara mendapatkan token baru menggunakan refresh_token →
... todo ...
Jika permintaan token baru tidak valid, akses aplikasi Anda akan dicabut oleh pengguna, dan pengguna harus logout lalu login kembali.