@kiauth/web-sdk 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # @kiauth/web-sdk
2
2
 
3
- Framework-agnostic "Login with Kiauth" button for any website (React, Vue, Angular,
4
- Svelte, or plain HTML). Renders a button, runs the QR approval flow, and hands your
5
- frontend a one-time authorization `code`. Your **server** exchanges that code for the
6
- verified user.
3
+ Three things, for any website (React, Vue, Angular, Svelte, or plain HTML):
7
4
 
8
- > Test backend: `https://api.kiauth.com` pass it as `baseUrl`.
5
+ 1. **Verified Human** — know the person signing up is a real, unique, Aadhaar-verified human. Not a bot, not a duplicate account.
6
+ 2. **Signup / Login** — passwordless "Login with Kiauth". The user approves on their phone with a biometric. No password database.
7
+ 3. **Session management + cross-app knowledge** — the user sees every app they are signed into and can revoke any of them; your server is told when they do. You can also surface logins that never went through Kiauth at all.
8
+
9
+ > Backend: `https://api.kiauth.com` — pass it as `baseUrl`.
9
10
 
10
11
  ## Install
11
12
 
12
13
  **Option A — script tag (no build tooling):**
13
14
  ```html
14
- <script src="/path/to/kiauth-sdk.min.js"></script>
15
+ <script src="https://unpkg.com/@kiauth/web-sdk@1.1.0/dist/kiauth-sdk.min.js"></script>
15
16
  <script>
16
17
  const kiauth = new KiauthSDK({
17
18
  clientId: 'YOUR_CLIENT_ID',
@@ -19,10 +20,14 @@ verified user.
19
20
  environment: 'production',
20
21
  baseUrl: 'https://api.kiauth.com'
21
22
  });
23
+
22
24
  kiauth.renderButton('#login-container', {
23
25
  text: 'Login with Kiauth',
26
+ // onSuccess receives the VERIFIED USER — the SDK completes the code exchange
27
+ // for you. It does not hand you a raw `code`.
24
28
  onSuccess: (user) => {
25
- // user.code -> send to YOUR backend to exchange (see below)
29
+ if (!KiauthSDK.isProductionTrustworthy(user)) return; // see §1
30
+ createSessionFor(user.userToken);
26
31
  },
27
32
  onError: (err) => console.error(err.message)
28
33
  });
@@ -30,12 +35,12 @@ verified user.
30
35
  <div id="login-container"></div>
31
36
  ```
32
37
 
33
- **Option B — local tarball (import workflow):**
38
+ **Option B — npm:**
34
39
  ```bash
35
- npm install ./kiauth-web-sdk-1.0.0.tgz
40
+ npm install @kiauth/web-sdk
36
41
  ```
37
42
  ```js
38
- import { KiauthSDK } from '@kiauth/web-sdk';
43
+ import { KiauthSDK, isProductionTrustworthy } from '@kiauth/web-sdk';
39
44
 
40
45
  const kiauth = new KiauthSDK({
41
46
  clientId: process.env.KIAUTH_CLIENT_ID,
@@ -45,6 +50,135 @@ const kiauth = new KiauthSDK({
45
50
  });
46
51
  ```
47
52
 
53
+ ---
54
+
55
+ ## 1. Verified Human
56
+
57
+ `onSuccess(user)` hands you a **graded** identity claim. Never trust the bare boolean.
58
+
59
+ ```js
60
+ kiauth.renderButton('#login-container', {
61
+ onSuccess: (user) => {
62
+ // ❌ Not enough — `kiauth_verified` is ALSO true for Kiauth's test identities.
63
+ // if (user.kiauth_verified) { ... }
64
+
65
+ // ✅ The check a production app should make:
66
+ if (!isProductionTrustworthy(user)) {
67
+ return showError('We could not verify your identity.');
68
+ }
69
+
70
+ // user.userToken — stable + pairwise. The SAME human always maps to this token
71
+ // at YOUR app, and to a different one at every other app.
72
+ // Key your account record on it.
73
+ // user.uniqueness — 'one_account_per_human'. One real human holds at most one
74
+ // account at your app — not even by deleting their Kiauth
75
+ // account and re-registering.
76
+ createAccount({ kiauthUserToken: user.userToken, name: user.name, email: user.email });
77
+ },
78
+ onError: (err) => console.error(err.message)
79
+ });
80
+ ```
81
+
82
+ `isProductionTrustworthy(user)` is exactly
83
+ `user.kiauth_verified && user.assurance_level === 'aadhaar' && !user.is_test_identity`.
84
+ It is also available as `KiauthSDK.isProductionTrustworthy` for script-tag users.
85
+
86
+ ### The `KiauthUser` object
87
+
88
+ | Field | Type | Meaning |
89
+ |---|---|---|
90
+ | `kiauth_verified` | boolean | Kiauth ran an identity check and it passed. **Also true for test identities.** |
91
+ | `verified` | boolean | Alias of `kiauth_verified`. Both are always present. |
92
+ | `identity_verified` | boolean | The durable fact that this human completed Aadhaar verification. Never decays. |
93
+ | `assurance_level` | `'aadhaar' \| 'human' \| 'test' \| 'none'` | How strong the check was. |
94
+ | `is_test_identity` | boolean | **TRUE for a fabricated identity from Kiauth's test mode. Reject in production.** |
95
+ | `uniqueness` | `'one_account_per_human'` | Kiauth's guarantee. |
96
+ | `userToken` | string | Stable, pairwise per (human, your app). Key your account on it. |
97
+ | `method` | `'aadhaar_okyc' \| 'peer' \| 'test_identity' \| 'none'` | |
98
+ | `verified_at` | string \| null | ISO timestamp of the identity check. |
99
+ | `verification_expires_at` | string \| null | When Kiauth will ask them to renew. |
100
+ | `assurance_age_days` | number \| null | How old the check is, in whole days. |
101
+ | `assurance_fresh` | boolean | Whether it is inside Kiauth's current freshness window. |
102
+
103
+ **Re-verification is Kiauth's job, not yours.** Kiauth periodically re-checks each
104
+ human; that cadence is a Kiauth↔user relationship. You are never forced to pay for a
105
+ fresh check because our clock ran out. If your own risk rules genuinely need a recent
106
+ check, use the verification API's `maxAssuranceAgeDays`. Otherwise read
107
+ `identity_verified` and let us worry about it.
108
+
109
+ ---
110
+
111
+ ## 2. Signup / Login
112
+
113
+ `renderButton()` runs the whole flow: shows a QR (desktop) or opens the Kiauth app
114
+ (mobile), waits for the biometric approval, exchanges the one-time code, and calls
115
+ `onSuccess(user)`.
116
+
117
+ ```js
118
+ kiauth.renderButton('#login-container', { onSuccess, onError });
119
+ ```
120
+
121
+ Or drive it yourself, without the button:
122
+
123
+ ```js
124
+ await kiauth.startLogin({ onSuccess, onError, onCancel });
125
+ ```
126
+
127
+ ### Where the code exchange happens
128
+
129
+ - **Without `clientSecret`** (recommended for browsers) — the SDK uses **PKCE** and
130
+ exchanges the code with no secret.
131
+ - **With `clientSecret` in config** — the SDK exchanges directly. Convenient for
132
+ server-side or sandbox use. **Never ship a client secret to a browser.**
133
+
134
+ Verify an access token later, from your server:
135
+ ```js
136
+ const { valid, user } = await kiauth.verifyToken(accessToken);
137
+ ```
138
+
139
+ > Prefer a redirect-based integration with your existing auth library (Auth.js, etc.)?
140
+ > Kiauth is also a standard **OpenID Connect** provider — see the dev-portal docs.
141
+ > The embedded button (this SDK) gives full white-label control; OIDC is the
142
+ > lowest-friction drop-in.
143
+
144
+ ---
145
+
146
+ ## 3. Session management + cross-app knowledge
147
+
148
+ The user sees every app they are signed into, inside the Kiauth app, and can revoke
149
+ any of them. When they do, Kiauth sends your server a **`SESSION_REVOKED`** webhook so
150
+ you can end your own session too — two-way logout.
151
+
152
+ You can also surface logins that **never went through Kiauth**, so the user's dashboard
153
+ is complete rather than partial.
154
+
155
+ > These methods require your `clientSecret` and **throw if called from a browser**.
156
+ > Run them on your server.
157
+
158
+ ```js
159
+ // A user signed in with Google / password / OTP — put it on their Kiauth dashboard.
160
+ await kiauth.reportSession({
161
+ kiauthUserToken: user.userToken,
162
+ externalSessionId: yourSessionId,
163
+ authMethod: 'GOOGLE_OAUTH', // EMAIL_PASSWORD | FACEBOOK | PHONE_OTP | APPLE | PASSKEY | UNKNOWN
164
+ deviceName: 'Chrome on MacBook',
165
+ deviceType: 'DESKTOP'
166
+ });
167
+
168
+ // Don't know their userToken? Report against a verified email instead.
169
+ await kiauth.reportSessionByEmail({ email, externalSessionId, authMethod: 'EMAIL_PASSWORD' });
170
+
171
+ // Backfill many at once.
172
+ await kiauth.reportBatchSessions(user.userToken, [ /* ... */ ]);
173
+
174
+ // The user logged out of YOUR app — remove it from their dashboard.
175
+ await kiauth.logout({ externalSessionId: yourSessionId });
176
+ ```
177
+
178
+ Webhooks to handle: `SESSION_CREATED`, `SESSION_REVOKED`, `PERMISSION_REVOKED`.
179
+
180
+ ---
181
+
48
182
  ## Config
49
183
 
50
184
  | Field | Required | Notes |
@@ -53,6 +187,7 @@ const kiauth = new KiauthSDK({
53
187
  | `scopes` | yes | Subset of `name`, `email`, `dob`, `gender`, `phone` |
54
188
  | `environment` | yes | `'production'` or `'sandbox'` |
55
189
  | `baseUrl` | recommended | Backend URL. Overrides the env default. `/api/v1` is appended automatically. |
190
+ | `clientSecret` | no | **Server / sandbox only.** Omit in browsers — the SDK uses PKCE. |
56
191
  | `redirectUri` | optional | Where to return after approval |
57
192
 
58
193
  ## Button customization (white-label)
@@ -83,27 +218,23 @@ kiauth.renderButton('#login-container', {
83
218
  });
84
219
  ```
85
220
 
86
- > Prefer a redirect-based integration with your existing auth library (Auth.js, etc.)?
87
- > Kiauth is also a standard **OpenID Connect** provider — see the dev-portal docs for the
88
- > "Login with Kiauth" OIDC option. The embedded button (this SDK) gives full white-label
89
- > control; OIDC is the lowest-friction drop-in.
90
-
91
- ## Server-side token exchange (required)
92
-
93
- The `code` from `onSuccess` is exchanged on **your backend** never expose your Client Secret in the browser.
94
-
95
- ```js
96
- const res = await fetch('https://api.kiauth.com/api/v1/auth/token', {
97
- method: 'POST',
98
- headers: { 'Content-Type': 'application/json' },
99
- body: JSON.stringify({
100
- grantType: 'authorization_code',
101
- code,
102
- clientId: process.env.KIAUTH_CLIENT_ID,
103
- clientSecret: process.env.KIAUTH_CLIENT_SECRET
104
- })
105
- });
106
- const { user } = await res.json(); // { name, email, verified, userToken }
107
- ```
108
-
109
- Use `user.userToken` (a stable per-user id) to create your own app session.
221
+ ---
222
+
223
+ ## Changelog
224
+
225
+ ### 1.1.0
226
+ - **Fixed:** `user.kiauth_verified` was `undefined` after a login. The backend's login
227
+ exchange emitted `verified` while this SDK's type declared `kiauth_verified`, so an
228
+ app checking the documented field silently treated every verified human as
229
+ unverified. Both spellings are now emitted and normalized.
230
+ - **Fixed:** the README claimed `onSuccess` hands you a `code` to exchange on your
231
+ server. It hands you the verified **user** — the SDK completes the exchange.
232
+ - **Added:** graded identity claims — `assurance_level`, `is_test_identity`,
233
+ `identity_verified`, `assurance_age_days`, `assurance_fresh`, `uniqueness`.
234
+ - **Added:** `isProductionTrustworthy(user)` (also `KiauthSDK.isProductionTrustworthy`).
235
+ - **Hardened:** every response is normalized and **fails closed**. A missing or
236
+ unrecognised field grades as unverified rather than reaching your `if` as
237
+ `undefined`.
238
+
239
+ ### 1.0.0
240
+ - Initial release.
@@ -1,3 +1,68 @@
1
+ /**
2
+ * Normalizes whatever the backend returned into a complete `KiauthUser`.
3
+ *
4
+ * Why this exists: `/auth/token` and `/auth/verify` historically disagreed on the
5
+ * key name — the login exchange said `verified`, introspection said
6
+ * `kiauth_verified`, and this SDK's type declared only the latter. Any app that
7
+ * wrote `if (user.kiauth_verified)` after a login was reading `undefined` and
8
+ * treating a verified human as unverified.
9
+ *
10
+ * The backend now emits both spellings. This function keeps the SDK correct against
11
+ * older backends too, and guarantees every field is defined rather than silently
12
+ * absent — an `undefined` in a trust decision is worse than a `false`.
13
+ *
14
+ * FAIL CLOSED: anything missing or unrecognised grades as unverified.
15
+ */
16
+ function normalizeUser(raw) {
17
+ const src = raw ?? {};
18
+ // Accept either spelling; require an explicit `true`, never a truthy accident.
19
+ const verified = src.kiauth_verified === true || src.verified === true;
20
+ const assurance = src.assurance_level === 'aadhaar' ||
21
+ src.assurance_level === 'human' ||
22
+ src.assurance_level === 'test' ||
23
+ src.assurance_level === 'none'
24
+ ? src.assurance_level
25
+ : // An older backend sent no grading. A bare `verified: true` cannot be
26
+ // distinguished from a test identity, so do not claim `aadhaar`.
27
+ verified
28
+ ? 'human'
29
+ : 'none';
30
+ const method = src.method === 'aadhaar_okyc' || src.method === 'test_identity' || src.method === 'peer' || src.method === 'none'
31
+ ? src.method
32
+ : 'none';
33
+ const expiresAt = src.verification_expires_at ?? src.verificationExpiry ?? null;
34
+ return {
35
+ name: src.name ?? src.displayName ?? null,
36
+ email: src.email ?? null,
37
+ kiauth_verified: verified,
38
+ verified,
39
+ // Absent on an older backend: fall back to `verified` rather than inventing a
40
+ // durable fact we were not told about.
41
+ identity_verified: src.identity_verified === true || verified,
42
+ assurance_level: assurance,
43
+ method,
44
+ verified_at: src.verified_at ?? null,
45
+ verification_expires_at: expiresAt,
46
+ verificationExpiry: expiresAt,
47
+ assurance_age_days: typeof src.assurance_age_days === 'number' ? src.assurance_age_days : null,
48
+ assurance_fresh: src.assurance_fresh === true,
49
+ // Fail closed: if the backend did not say, assume nothing. A missing flag must
50
+ // not read as "definitely a real human".
51
+ is_test_identity: src.is_test_identity === true,
52
+ uniqueness: 'one_account_per_human',
53
+ userToken: src.userToken ?? ''
54
+ };
55
+ }
56
+ /**
57
+ * The check a production app should make. True only for a real, Aadhaar-verified,
58
+ * non-fabricated human.
59
+ *
60
+ * if (!isProductionTrustworthy(user)) return reject();
61
+ */
62
+ function isProductionTrustworthy(user) {
63
+ return user.kiauth_verified && user.assurance_level === 'aadhaar' && !user.is_test_identity;
64
+ }
65
+
1
66
  class KiauthApi {
2
67
  constructor(config) {
3
68
  this.baseUrl = KiauthApi.resolveBaseUrl(config);
@@ -99,7 +164,10 @@ class KiauthApi {
99
164
  const err = await res.json().catch(() => ({}));
100
165
  throw { code: err.code || 'TOKEN_EXCHANGE_FAILED', message: err.message || 'Failed to exchange authorization code.' };
101
166
  }
102
- return res.json();
167
+ const body = await res.json();
168
+ // Never hand the caller a raw response: normalizeUser guarantees every field is
169
+ // defined, so `user.kiauth_verified` can never be a silent `undefined`.
170
+ return { ...body, user: normalizeUser(body.user) };
103
171
  }
104
172
  async verifyToken(token) {
105
173
  const res = await fetch(`${this.baseUrl}/auth/verify`, {
@@ -113,7 +181,10 @@ class KiauthApi {
113
181
  if (!res.ok) {
114
182
  return { valid: false };
115
183
  }
116
- return res.json();
184
+ const body = await res.json();
185
+ if (!body.valid)
186
+ return { valid: false };
187
+ return { valid: true, user: normalizeUser(body.user) };
117
188
  }
118
189
  async reportSession(clientId, clientSecret, options) {
119
190
  const creds = btoa(`${clientId}:${clientSecret}`);
@@ -3679,6 +3750,14 @@ class KiauthSDK {
3679
3750
  this.modal.close();
3680
3751
  }
3681
3752
  }
3753
+ /**
3754
+ * The check a production app should make on every login.
3755
+ *
3756
+ * `user.kiauth_verified` alone is NOT enough — it is also true for Kiauth's test
3757
+ * identities. Exposed as a static so script-tag (UMD) users can reach it too:
3758
+ * if (!KiauthSDK.isProductionTrustworthy(user)) return;
3759
+ */
3760
+ KiauthSDK.isProductionTrustworthy = isProductionTrustworthy;
3682
3761
 
3683
- export { KiauthSDK };
3762
+ export { KiauthSDK, isProductionTrustworthy, normalizeUser };
3684
3763
  //# sourceMappingURL=kiauth-sdk.js.map