従業員は自分のJS Staffアカウントであなたのアプリにログインします。あなたのアプリがパスワードを見ることは一切ありません。
{
"user_id": 3,
"nama": "Sela",
"email": "sela@contoh.com",
"peran": "staff",
"tenant_id": 1,
"tenant_nama": "Jidanshoppu",
"tenant_slug": "jidanshoppu"
}
それだけです。peran(役割)には staff、supervisor、owner のいずれかが入ります。あなたのアプリは誰のパスワード・給与・勤怠・前借り・口座も読み取れません。
JS Staffのプラットフォーム管理者から取得します(/super/apps.phpで登録されます):
| 値 | 例 |
|---|---|
client_id | app_a1b2c3d4e5f6a7b8 |
client_secret | sec_1a2b3c… ——一度だけ表示されます |
登録時にredirect URIを申告する必要があります。ルールは厳格です:
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;
ボタンはこれだけで十分です: <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 がブラウザを経由するのは、その秘密を全員と共有するのと同じことです。 Refererヘッダーに残ります。 user_id はグローバルに一意で、tenant_id は会社を示します。どちらも安定しており、名前が変わっても決して変わりません。
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);
初回のフロー:
jss_user_id = :user_id でアカウントを検索 → 見つかった?ログイン。tenant_slug をあなた側の店舗と照合し、スタッフアカウントを作成して jss_user_id を保存してください。tenant_slug が未知の場合?拒否してください。SSOのデータから新しい店舗を黙って作成しないでください——JS Staffのアカウントを持つ誰でもそれを引き起こせてしまいます。state を使用してください:ランダムに生成し、セッションに保存し、hash_equals で照合し、一度限り使用します。redirect_uri はステップ3と4で完全に一致させてください。| 症状 | よくある原因 |
|---|---|
| 「戻りアドレスが一致しません」ページ | redirect_uri が登録内容と異なります。末尾のスラッシュ、http/https、クエリ文字列を確認してください。 |
| 「不明なアプリケーション」ページ | client_id が誤っているか、プラットフォーム管理者によってアプリが取り消されています。 |
/sso/token.php で 401 |
コードの有効期限切れ(60秒超過)、すでに使用済み、client_secret が誤っている、またはステップ3と redirect_uri が異なる。 |
?error=access_denied 付きで戻る |
従業員が拒否を押しました。バグではありません。 |
401 はあえてすべての原因に同じメッセージを使っています——「コードが間違っている」と「コードが使用済み」を区別すると、攻撃者にどのコードが存在したかを推測する手がかりを与えてしまい、正しく統合できている人の助けにもなりません。上記の4点を一つずつ確認してください。