← API notifikasi JS Staff

„Mit JS Staff anmelden“ einrichten

Mitarbeiter melden sich mit ihrem JS-Staff-Konto bei deiner App an. Deine App sieht niemals ihr Passwort.

Was du bekommst

{
  "user_id": 3,
  "nama": "Sela",
  "email": "sela@contoh.com",
  "peran": "staff",
  "tenant_id": 1,
  "tenant_nama": "Jidanshoppu",
  "tenant_slug": "jidanshoppu"
}

Das ist alles. peran (Rolle) enthält staff, supervisor oder owner. Deine App kann nicht das Passwort, Gehalt, die Anwesenheit, Vorschüsse oder das Bankkonto von irgendjemandem lesen.

Warum eine Weiterleitung und keine „E-Mail+Passwort senden“-API. Eine solche API würde das Neu-Geräte-OTP untergraben, auf das sich dieses Portal verlässt. Der ganze Sinn von OTP ist, dass ein Passwort allein nicht zum Anmelden reicht — sobald eine Tür nur ein Passwort akzeptiert, geht ein Passwortdieb einfach durch diese Tür.

Mit einer Weiterleitung gibt es keine solche Tür: Die Anmeldung findet immer auf unserer Seite statt, samt OTP und den vertrauenswürdigen Geräten.

Der Ablauf

[1] Mitarbeiter klickt in deiner App auf „Mit JS Staff anmelden“ ↓ [2] Du leitest ihn weiter zu https://staff.jidanshoppu.com/sso/authorize.php?... ↓ (dort meldet er sich an + OTP bei neuem Gerät + Zustimmungsbildschirm) [3] Er kehrt zurück zu sso-callback.php?code=...&state=... ↓ [4] Dein SERVER tauscht code + secret unter /sso/token.php ← Server-zu-Server ↓ [5] Du erhältst seine Identität → du baust deine eigene Sitzung auf

1 · Diese zwei Werte anfordern

Vom Plattform-Administrator von JS Staff (er registriert es unter /super/apps.php):

WertBeispiel
client_idapp_a1b2c3d4e5f6a7b8
client_secretsec_1a2b3c… — wird nur einmal angezeigt

Du musst deine Redirect-URI bei der Registrierung angeben. Die Regel ist streng:

Warum so streng. Dies ist die wichtigste Schutzmaßnahme im gesamten SSO-Ablauf. Lockere sie auch nur ein wenig — Präfix, Subdomain, Wildcard — und ein Angreifer muss deinem Mitarbeiter nur einen Autorisierungslink mit seiner eigenen Rückrufadresse schicken. Das Opfer meldet sich wie gewohnt auf unserer echten Seite an, nichts wirkt verdächtig, und der Code landet auf dem Server des Angreifers.

2 · Konfiguration

<?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';

3 · Login-Button

<?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;

Der Button genügt: <a href="/sso-mulai.php">Masuk pakai JS Staff</a>

4 · Callback

<?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;
Dieser Schritt muss vom SERVER kommen, nicht vom Browser. Ein client_secret, das durch den Browser läuft, ist dasselbe wie ein mit jedem geteiltes Geheimnis.

Tausche den Code sofort ein. Er lebt 60 Sekunden und ist einmalig verwendbar — er läuft über die Browser-URL, und URLs landen im Verlauf, in Server-Logs und im Referer-Header.

5 · Zuordnung zu einem Konto auf deiner Seite

user_id ist global eindeutig, tenant_id kennzeichnet die Firma. Beide sind stabil — sie ändern sich nie, auch wenn sich Namen ändern.

Nicht per E-Mail abgleichen. Die E-Mail kann vom Inhaber geändert werden, und ein Abgleich per E-Mail bedeutet, dass ein Konto in dem Moment, in dem die E-Mail geändert wird, stillschweigend den Besitzer wechseln kann. Speichere 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);

Ablauf beim ersten Mal:

  1. Suche das Konto mit jss_user_id = :user_id → gefunden? anmelden.
  2. Nicht gefunden? Gleiche tenant_slug mit einem Shop auf deiner Seite ab, erstelle das Mitarbeiterkonto, speichere dessen jss_user_id.
  3. tenant_slug unbekannt? Ablehnen. Erstelle nicht stillschweigend einen neuen Shop aus SSO-Daten — das könnte jeder mit einem JS-Staff-Konto auslösen.

Was nötig ist & was nicht

Erforderlich

Nicht nötig

Bei Fehlschlag

SymptomMeist verursacht durch
Seite „Rückgabeadresse stimmt nicht überein“ redirect_uri weicht vom registrierten Wert ab. Prüfe abschließenden Schrägstrich, http vs. https, Query-String.
Seite „Unbekannte Anwendung“ client_id ist falsch, oder die App wurde vom Plattform-Administrator widerrufen.
401 bei /sso/token.php Code abgelaufen (>60 Sekunden), bereits verwendet, client_secret falsch, oder redirect_uri weicht von Schritt 3 ab.
Kehrt zurück mit ?error=access_denied Der Mitarbeiter hat auf Ablehnen geklickt. Kein Bug.

401 verwendet absichtlich dieselbe Meldung für jede Ursache — die Unterscheidung von „falscher Code“ und „Code bereits verwendet“ würde einem Angreifer verraten, welche Codes je existiert haben, und hilft niemandem, dessen Integration korrekt ist. Prüfe die vier obigen Punkte einzeln.

Testen

  1. Bitte den Plattform-Administrator, eine zweite App nur für Tests zu registrieren.
  2. Melde dich mit einem echten Mitarbeiterkonto an.
  3. Der Zustimmungsbildschirm sollte einmal erscheinen. Beim zweiten Mal sollte er direkt durchlaufen.
  4. Widerrufe den Zugriff unter Mein Profil → Apps, die dein Konto nutzen dürfen → der Zustimmungsbildschirm sollte erneut erscheinen.