Les employés se connectent à votre application avec leur compte JS Staff. Votre application ne voit jamais leur mot de passe.
{
"user_id": 3,
"nama": "Sela",
"email": "sela@contoh.com",
"peran": "staff",
"tenant_id": 1,
"tenant_nama": "Jidanshoppu",
"tenant_slug": "jidanshoppu"
}
C’est tout. peran (rôle) contient staff, supervisor ou owner. Votre application ne peut pas lire le mot de passe, le salaire, la présence, les avances ou le compte bancaire de qui que ce soit.
Auprès de l’administrateur de la plateforme JS Staff (il l’enregistre sur /super/apps.php) :
| Valeur | Exemple |
|---|---|
client_id | app_a1b2c3d4e5f6a7b8 |
client_secret | sec_1a2b3c… — affiché une seule fois |
Vous devez indiquer votre redirect URI lors de l’inscription. La règle est stricte :
https://<?php // sso-config.php — JANGAN di-commit. Isi dari env var. const SSO_BASE = 'https://staff.jidanshoppu.com'; const SSO_CLIENT_ID = 'app_xxxxxxxxxxxxxxxx'; const SSO_SECRET = 'sec_xxxxxxxxxxxxxxxx'; const SSO_REDIRECT = 'https://order.jidanshoppu.com/sso-callback.php';
<?php
// sso-mulai.php
require 'sso-config.php';
session_start();
/*
* `state` WAJIB. Acak, disimpan di sesi, dicocokkan lagi di callback.
* Tanpa ini, penyerang bisa mengirim link callback berisi kode MILIK DIA ke korban —
* korban jadi login sebagai akun penyerang, lalu mengetik data ke akun itu tanpa sadar.
*/
$_SESSION['sso_state'] = bin2hex(random_bytes(16));
$url = SSO_BASE . '/sso/authorize.php?' . http_build_query([
'client_id' => SSO_CLIENT_ID,
'redirect_uri' => SSO_REDIRECT,
'state' => $_SESSION['sso_state'],
]);
header('Location: ' . $url);
exit;
Le bouton suffit : <a href="/sso-mulai.php">Masuk pakai JS Staff</a>
<?php
// sso-callback.php
require 'sso-config.php';
session_start();
// --- Orangnya menolak di layar persetujuan ---
if (isset($_GET['error'])) {
exit('Login dibatalkan.');
}
// --- Cocokkan state DULU, sebelum menyentuh code ---
$state = (string)($_GET['state'] ?? '');
if ($state === ''
|| !isset($_SESSION['sso_state'])
|| !hash_equals($_SESSION['sso_state'], $state)) {
// hash_equals, bukan ===: perbandingan biasa berhenti di huruf pertama yang beda,
// dan selisih waktunya bisa dipakai menebak nilainya.
exit('State tidak cocok. Ulangi dari awal.');
}
unset($_SESSION['sso_state']); // sekali pakai
$code = (string)($_GET['code'] ?? '');
if ($code === '') {
exit('Kode tidak ada.');
}
// --- Tukar kode jadi identitas (SERVER ke server) ---
$ch = curl_init(SSO_BASE . '/sso/token.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POSTFIELDS => http_build_query([
'client_id' => SSO_CLIENT_ID,
'client_secret' => SSO_SECRET,
'code' => $code,
'redirect_uri' => SSO_REDIRECT, // harus sama dengan langkah 3
]),
]);
$body = curl_exec($ch);
$kode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($kode !== 200) {
// Jangan tampilkan $body apa adanya ke pengguna — itu pesan untuk kamu, bukan dia.
error_log('SSO gagal: ' . $kode . ' ' . $body);
exit('Login gagal. Coba lagi.');
}
$data = json_decode((string)$body, true);
if (!is_array($data) || empty($data['ok'])) {
exit('Login gagal. Coba lagi.');
}
$u = $data['user'];
// --- Mulai dari sini urusan kamu ---
session_regenerate_id(true); // cegah session fixation
$_SESSION['jss_user_id'] = $u['user_id'];
$_SESSION['jss_tenant_id'] = $u['tenant_id'];
$_SESSION['nama'] = $u['nama'];
$_SESSION['peran'] = $u['peran'];
header('Location: /app.php');
exit;
client_secret qui transite par le navigateur équivaut à un secret partagé avec tout le monde. Referer. user_id est unique au niveau global, tenant_id identifie l’entreprise. Les deux sont stables — ils ne changent jamais, même si les noms changent.
jss_user_id.ALTER TABLE staff ADD COLUMN jss_user_id INTEGER NULL; ALTER TABLE staff ADD COLUMN jss_tenant_id INTEGER NULL; CREATE UNIQUE INDEX uq_staff_jss ON staff (jss_user_id);
Flux la première fois :
jss_user_id = :user_id → trouvé ? connectez-le.tenant_slug à une boutique de votre côté, créez le compte du membre, enregistrez son jss_user_id.tenant_slug non reconnu ? Rejetez-le. Ne créez pas silencieusement une nouvelle boutique à partir des données SSO — n’importe qui avec un compte JS Staff pourrait le déclencher.state : aléatoire, en session, comparé avec hash_equals, à usage unique.redirect_uri doit être identique aux étapes 3 et 4.| Symptôme | Généralement causé par |
|---|---|
| Page « L’adresse de retour ne correspond pas » | redirect_uri diffère de celui enregistré. Vérifiez la barre oblique finale, http vs https, la chaîne de requête. |
| Page « Application inconnue » | client_id est incorrect, ou l’application a été révoquée par l’administrateur de la plateforme. |
401 sur /sso/token.php |
Code expiré (>60 secondes), déjà utilisé, client_secret incorrect, ou redirect_uri différent de l’étape 3. |
Revient avec ?error=access_denied |
L’employé a cliqué sur Refuser. Ce n’est pas un bug. |
401 utilise délibérément le même message pour toutes les causes — distinguer « code incorrect » de « code déjà utilisé » donnerait à un attaquant un moyen de cartographier les codes ayant existé, et n’aide personne dont l’intégration est correcte. Vérifiez les quatre points ci-dessus un par un.