npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@starasia/sso

v3.0.1

Published

Starasia SSO for React SPAs — OIDC authorization code + PKCE (oidc-client-ts) against sso-api.

Readme

@starasia/sso v3

Login Starasia SSO untuk React SPA memakai OIDC authorization code + PKCE (oidc-client-ts) langsung ke sso-api sebagai public client — tanpa client secret dan tanpa backend auth per aplikasi.

Token ditukar atas nama clientId aplikasi, sehingga sesi, refresh token, dan audit di SSO tercatat sebagai aplikasi tersebut (v1 menukar token lewat backend lain sehingga sesi bisa tercatat sebagai aplikasi lain).

v3 berisi perubahan breaking (refresh token tidak lagi diberikan ke kode app). Lihat Migrasi dari v2 dan CHANGELOG.md.

Keamanan: SDK ini bukan batas otorisasi

ProtectedRoute, useProfile(), dan semua klaim ID token (name, email, role, dll.) hanya untuk UX — menentukan apa yang ditampilkan. Semuanya berjalan di browser dan bisa dimanipulasi user, jadi bukan batas keamanan.

Otorisasi wajib ditegakkan di backend: setiap request API memverifikasi access token (signature, iss, aud, exp, scope/role) memakai createResourceServer dari @starasia/sso-api. Jangan kirim ID token ke backend sebagai bukti akses, dan jangan anggap data aman hanya karena halamannya dibungkus ProtectedRoute.

Prasyarat di SSO

Di client aplikasi (admin-dashboard → Applications):

  • isPublic = true, requirePkce = true
  • Redirect URIs: <origin>/callback untuk setiap origin (mis. http://localhost:5173/callback, https://app-dev.web.app/callback)
  • Post-Logout Redirect URIs: <origin>/
  • Bila memakai opsi resource: daftarkan URI API tersebut di allowlist resource client.

authority = issuer sso-api, sama persis dengan issuer di <authority>/.well-known/openid-configuration.

Instalasi

pnpm add @starasia/sso

Peer: react ≥18. @tanstack/react-query v5 opsional (hanya untuk createAuthErrorSubscribers).

Paket ini publik di npm (publishConfig.access: "public"), jadi app bisa memasangnya tanpa token npm.

Pemakaian

// core/configs/app.ts
import { setupSSO } from '@starasia/sso';

export const ssoConfig = setupSSO({
  authority: import.meta.env.SPT_SSO_API_BASE_URL,
  clientId: import.meta.env.SPT_CLIENT_ID
  // resource: 'https://api.example.com/hris' — audience access token (lihat di bawah)
  // redirectUri: default `${origin}/callback`
  // postLogoutRedirectUri: default `${origin}/`
  // scope: default 'openid profile email offline_access'
  // appName: teks di bawah logo pada splash
});
export const SSO = ssoConfig.components;
export const { useProfile, useLogout, useAuth } = ssoConfig.hooks;
// router — callback DI LUAR ProtectedRoute, sebelum route catch-all
createBrowserRouter([
  { path: '/callback', element: <SSO.CallbackRoute /> },
  { path: '/', element: <SSO.ProtectedRoute><App /></SSO.ProtectedRoute>, children: [/* ... */] }
]);
// request backend — token selalu valid (auto-renew via refresh token)
const token = await ssoConfig.actions.getAccessToken();
fetch(url, { headers: { Authorization: `Bearer ${token}` } });

// react-query: 401 → perbarui sesi lalu refetch; gagal → login ulang
const { unsubscribeAll } = ssoConfig.query.createAuthErrorSubscribers(queryClient);

Konfigurasi setupSSO(config)

| Opsi | Default | Keterangan | |---|---|---| | authority | — (wajib) | Issuer sso-api, sama persis dengan issuer di discovery. | | clientId | — (wajib) | Client ID aplikasi (public client, PKCE). | | redirectUri | ${origin}/callback | Harus terdaftar di Redirect URIs client. | | postLogoutRedirectUri | ${origin}/ | Harus terdaftar di Post-Logout Redirect URIs client. | | scope | openid profile email offline_access | sso-api hanya memberikan keempat scope ini; scope lain diabaikan. | | resource | — | Resource indicator (RFC 8707): URI absolut API, dikirim sebagai resource di /authorize. Lihat Resource indicator. | | extraQueryParams | — | Parameter tambahan untuk /authorize (digabung dengan resource; tidak dikirim ke end_session). | | storage | window.localStorage | Storage token & state. | | appName | Star Perkasa Technology | Teks di bawah logo pada splash layar penuh. | | loadingFallback | splash layar penuh | Tampilan saat gate memeriksa / memperbarui sesi. |

Resource indicator (resource)

setupSSO({ authority, clientId, resource: 'https://api.example.com/hris' });
  • Dikirim sebagai parameter resource di /authorize (digabung dengan extraQueryParams; bila keduanya berisi resource, opsi resource yang dipakai).
  • Nilainya harus ada di allowlist resource client di SSO; selain itu sso-api membalas error=invalid_target dan CallbackRoute menampilkan error login.
  • aud access token = nilai resource dalam bentuk baku seperti yang tampil di admin dashboard (origin tanpa path mendapat / di akhir, jadi lebih aman memakai identifier ber-path). Backend lalu memakai identifier yang sama sebagai audiences di createResourceServer (@starasia/sso-api), mis. createResourceServer({ /* … */ audiences: ['https://api.example.com/hris'] }), sehingga token untuk API/aplikasi lain ditolak.

API setupSSO(config)

| Bagian | Isi | |---|---| | components | ProtectedRoute (gate: tanpa sesi → /authorize; token habis → refresh dulu), CallbackRoute (tukar code → token, kembali ke halaman asal; prop renderError) | | hooks | useProfile() → { data: SSOProfile, isLoading } (klaim id_token), useAuth() → AuthModel (accessToken, idToken, expiresAt), useLogout() → { mutate, logout }, useAuthUser() → { user, loading } (user.refresh_token selalu undefined) | | actions | login(returnTo?), logout() (cabut refresh token → end_session), getAccessToken(), renew() (hasil tanpa refresh_token) | | storage | getMainTokenFromStorage() → AuthModel \| null (sinkron, bisa sudah kedaluwarsa) | | query | createAuthErrorSubscribers(queryClient, { isUnauthorized?, onUnauthorized? }) | | userManager | instance oidc-client-ts untuk kebutuhan lanjutan — akses mentah, termasuk refresh token. Jangan panggil signinSilent() langsung (melewati lock antar-tab di renew()). |

Juga diekspor: tipe SSOConfig, AuthModel, SSOProfile, User, dan AuthLayout.

Refresh token hanya dikelola SDK (disimpan oidc-client-ts di storage) dan tidak pernah dikembalikan oleh hook, storage.getMainTokenFromStorage(), maupun actions.renew().

Splash & loading

Animasi logo SPT di tengah papan sirkuit (CSS murni, logo tersemat di bundle — tanpa aset atau stylesheet tambahan di app).

| Ekspor | Kegunaan | |---|---| | LoadingFallback | Splash layar penuh selama ter-mount. Default loadingFallback gate SSO. | | useSplash(active) | Tahan splash layar penuh selama active — mis. saat app memeriksa akses user setelah login, agar splash berlanjut sampai halaman pertama siap. | | Loading | Versi inline (mis. di dalam tabel). Prop width, label. | | Splash | Komponen dasar (state, label, fullscreen, size). |

  • Splash layar penuh adalah satu instance di document.body, jadi perpindahan /callback → halaman tujuan → cek akses app memakai splash yang sama (tidak mulai ulang).
  • Tampil minimal 1,5 detik sejak splash muncul (0,5 detik saat prefers-reduced-motion) agar animasi pembuka selalu selesai, juga saat bundle app lambat dimuat; lalu keluar dengan animasi ~1 detik. Pemegang yang mengambil alih (mis. /callback → gate halaman) tidak memulai hitungan dari awal. Scroll dikunci selama tampil.
  • Teks di bawah logo: setupSSO({ appName }) (default Star Perkasa Technology).
  • Warna/font bisa di-override: --sa-splash-navy, --sa-splash-azure, --sa-splash-font.

Perilaku penting:

  • Perpanjangan token. Refresh token dirotasi sso-api setiap dipakai, dan refresh token yang dipakai ulang dianggap pencurian (seluruh family dicabut → semua tab harus login ulang). Karena itu automaticSilentRenew oidc-client-ts dimatikan; event accessTokenExpiring (±60 detik sebelum habis), ProtectedRoute, getAccessToken(), dan handler 401 semuanya lewat renew().
  • Multi-tab. renew() memakai Web Locks (navigator.locks, nama starasia-sso-refresh:<authority>:<clientId>) sehingga hanya satu tab yang refresh pada satu waktu. Di dalam lock storage dibaca ulang: bila tab lain sudah refresh, token barunya dipakai tanpa request. Tanpa Web Locks (browser lama, atau origin http:// non-localhost) → hanya dedupe dalam tab.
  • Sinkronisasi antar-tab. Token diperbarui di tab lain → tab ini memuat token baru (event storage). User dihapus di tab lain (logout) → ProtectedRoute menyembunyikan UI dan redirect ke login.
  • Logout. Refresh token dicabut dulu (POST /revoke, RFC 7009, dengan client_id), lalu redirect ke end_session dengan client_id (+ id_token_hint bila ada; client_id tidak pernah terkirim dua kali). Bila redirect gagal disiapkan (mis. discovery tidak bisa diambil), user lokal dihapus dan browser tetap diarahkan ke end_session (endpoint discovery, atau <authority>/end_session) — supaya sesi di SSO ikut berakhir, bukan "logout palsu" yang langsung masuk lagi.
  • Token habis dan refresh gagal → ProtectedRoute mengarahkan ke login ulang.
  • 401 berulang: setelah renew berhasil tapi 401 lagi dalam 10 detik → login ulang; 401 lagi dalam 60 detik setelah login ulang → redirect dihentikan (log error) untuk mencegah loop bila backend/gateway menolak token.
  • returnTo hanya menerima path relatif same-origin (cegah open redirect).

Migrasi dari v2

| v2 | v3 | |---|---| | AuthModel.refreshToken (useAuth(), storage.getMainTokenFromStorage()) | dihapus — refresh token hanya dikelola SDK | | useAuthUser().user.refresh_token, actions.renew() → User dengan refresh_token | refresh_token selalu undefined | | automaticSilentRenew oidc-client-ts (timer per tab) | accessTokenExpiring → renew() dengan Web Lock antar-tab | | logout() me-renew token kedaluwarsa demi id_token_hint | tanpa renew; refresh token dicabut lalu end_session dengan client_id | | logout() gagal → hanya hapus user lokal, ke postLogoutRedirectUri | hapus user lokal, lalu tetap ke end_session | | — | opsi resource (RFC 8707) |

Langkah per app:

  1. Hapus semua pemakaian refreshToken / refresh_token dari SDK. Untuk memanggil API cukup actions.getAccessToken(); perpanjangan sesi ditangani SDK.
  2. Bila memakai userManager langsung: jangan panggil signinSilent() atau startSilentRenew() — pakai actions.renew().
  3. Opsional: set resource dan samakan audiences di backend (createResourceServer).
  4. Pastikan backend menegakkan otorisasi dari access token (lihat Keamanan).

Migrasi dari v1

| v1 | v2 | |---|---| | setupSSO({ app: { api, key, name, sso: { clientId, redirectUri }, storage } }) | setupSSO({ authority, clientId }) | | api.baseUrl = company-management-service / backend /auth/* | authority = issuer sso-api | | sso.redirectUri = halaman login SSO | redirectUri = <origin>/callback milik app (default) | | ?authToken= + POST /auth/callback | /authorize + POST /token (PKCE) | | key storage access / auth / otp | storage oidc-client-ts (oidc.user:<authority>:<clientId>) | | useValidate, useSilentAuth, useAuthCallback, hooks.logout(token) | dihapus — ditangani ProtectedRoute / actions.logout() | | storage.getMainTokenFromStorage<T>() | storage.getMainTokenFromStorage() atau actions.getAccessToken() | | DecodeToken.organization / role | belum ada (sso-api belum mengirim klaim org) | | peer react-router-dom | tidak dibutuhkan | | Loading / LoadingFallback (lottie) | animasi logo SPT (CSS); Loading kini inline, LoadingFallback layar penuh; dependensi lottie-web dihapus |

Langkah per app: tambah route /callback, daftarkan Redirect/Post-Logout URIs di client SSO, ganti env (SPT_SSO_API_BASE_URL = issuer; SPT_REDIRECT_URI, SPT_STORAGE_AUTH, SPT_AUTH_SERVICE tidak dipakai lagi). Lalu ikuti Migrasi dari v2.

Belum didukung: pemilihan organisasi (multi-org). App yang membutuhkan organization di token tetap di v1 sampai sso-api menyediakan klaim/alur org.

Build

pnpm install
pnpm type-check
pnpm test    # vitest
pnpm build   # dist/starasia-sso.{es,umd}.js + dist/types (tanpa source map)

License

Private internal package. Redistribution is restricted.