@drmhse/sso-sdk 0.2.1 → 0.2.3
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 +170 -18
- package/dist/index.d.mts +0 -1
- package/dist/index.d.ts +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@ A zero-dependency, strongly-typed TypeScript SDK for interacting with the multi-
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Zero Dependencies**: Built on native `fetch` API - no external dependencies
|
|
8
|
-
- **Framework Agnostic**: Pure TypeScript - works in any JavaScript environment
|
|
9
|
-
- **Strongly Typed**: Complete TypeScript definitions for all API endpoints
|
|
10
|
-
- **Stateless Design**: No internal state management - integrates with any state solution
|
|
11
|
-
- **Predictable Error Handling**: Custom `SsoApiError` class with structured error information
|
|
12
|
-
- **Modern**: Supports Node.js 18+ and all modern browsers
|
|
7
|
+
- **Zero Dependencies**: Built on native `fetch` API - no external dependencies.
|
|
8
|
+
- **Framework Agnostic**: Pure TypeScript - works in any JavaScript environment.
|
|
9
|
+
- **Strongly Typed**: Complete TypeScript definitions for all API endpoints.
|
|
10
|
+
- **Stateless Design**: No internal state management - integrates with any state solution.
|
|
11
|
+
- **Predictable Error Handling**: Custom `SsoApiError` class with structured error information.
|
|
12
|
+
- **Modern**: Supports Node.js 18+ and all modern browsers.
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -97,7 +97,7 @@ const deviceAuth = await sso.auth.deviceCode.request({
|
|
|
97
97
|
console.log(`Visit: ${deviceAuth.verification_uri}`);
|
|
98
98
|
console.log(`Enter code: ${deviceAuth.user_code}`);
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// 4. Poll for the token
|
|
101
101
|
const pollForToken = async () => {
|
|
102
102
|
// Polling logic...
|
|
103
103
|
};
|
|
@@ -109,11 +109,12 @@ pollForToken();
|
|
|
109
109
|
// 2. After user enters the code, verify it to get context
|
|
110
110
|
const context = await sso.auth.deviceCode.verify(userEnteredCode);
|
|
111
111
|
|
|
112
|
-
// Redirect user to the appropriate login flow
|
|
112
|
+
// 3. Redirect user to the appropriate login flow, passing the user_code
|
|
113
|
+
// This links the browser session to the device waiting for authorization.
|
|
113
114
|
const loginUrl = sso.auth.getLoginUrl('github', {
|
|
114
115
|
org: context.org_slug,
|
|
115
116
|
service: context.service_slug,
|
|
116
|
-
user_code: userEnteredCode,
|
|
117
|
+
user_code: userEnteredCode, // CRITICAL: Pass user_code here
|
|
117
118
|
});
|
|
118
119
|
window.location.href = loginUrl; // User logs in, authorizing the device
|
|
119
120
|
```
|
|
@@ -145,11 +146,127 @@ localStorage.removeItem('sso_token');
|
|
|
145
146
|
localStorage.removeItem('sso_refresh_token');
|
|
146
147
|
```
|
|
147
148
|
|
|
149
|
+
## Validating Tokens in Your Backend
|
|
150
|
+
|
|
151
|
+
The SSO platform uses **RS256** (RSA with SHA-256) asymmetric signing for JWTs. This means your backend services can validate JWT signatures without needing access to any shared secrets.
|
|
152
|
+
|
|
153
|
+
### How It Works
|
|
154
|
+
|
|
155
|
+
1. **Fetch the JWKS**: The SSO platform exposes a public JWKS (JSON Web Key Set) endpoint at `/.well-known/jwks.json` containing the public RSA key(s).
|
|
156
|
+
2. **Cache the Keys**: Fetch and cache the JWKS in your backend to avoid repeated requests.
|
|
157
|
+
3. **Verify Tokens**: When a client sends a JWT, extract the `kid` (Key ID) from the token header, find the matching key in your cached JWKS, and verify the signature.
|
|
158
|
+
4. **Validate Claims**: After signature verification, validate token claims like `exp` (expiration), `iss` (issuer), and `aud` (audience).
|
|
159
|
+
|
|
160
|
+
### Node.js/Express Example
|
|
161
|
+
|
|
162
|
+
Here's a complete example of JWT validation middleware:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { expressjwt } from 'express-jwt';
|
|
166
|
+
import jwksRsa from 'jwks-rsa';
|
|
167
|
+
|
|
168
|
+
// Configure JWKS client to fetch public keys
|
|
169
|
+
const jwksClient = jwksRsa({
|
|
170
|
+
cache: true,
|
|
171
|
+
rateLimit: true,
|
|
172
|
+
jwksRequestsPerMinute: 5,
|
|
173
|
+
jwksUri: 'https://sso.example.com/.well-known/jwks.json'
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Function to get signing key from JWKS
|
|
177
|
+
function getKey(header, callback) {
|
|
178
|
+
jwksClient.getSigningKey(header.kid, (err, key) => {
|
|
179
|
+
if (err) {
|
|
180
|
+
return callback(err);
|
|
181
|
+
}
|
|
182
|
+
const signingKey = key.getPublicKey();
|
|
183
|
+
callback(null, signingKey);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// JWT validation middleware
|
|
188
|
+
const requireAuth = expressjwt({
|
|
189
|
+
secret: getKey,
|
|
190
|
+
algorithms: ['RS256'],
|
|
191
|
+
credentialsRequired: true,
|
|
192
|
+
getToken: (req) => {
|
|
193
|
+
if (req.headers.authorization?.startsWith('Bearer ')) {
|
|
194
|
+
return req.headers.authorization.substring(7);
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Use in your routes
|
|
201
|
+
app.get('/api/protected', requireAuth, (req, res) => {
|
|
202
|
+
// req.auth contains the decoded JWT claims
|
|
203
|
+
const { sub, email, org, service } = req.auth;
|
|
204
|
+
res.json({ message: `Hello ${email}` });
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Manual Validation (Node.js)
|
|
209
|
+
|
|
210
|
+
If you prefer to validate manually without middleware:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import jwt from 'jsonwebtoken';
|
|
214
|
+
import jwksRsa from 'jwks-rsa';
|
|
215
|
+
|
|
216
|
+
const jwksClient = jwksRsa({
|
|
217
|
+
jwksUri: 'https://sso.example.com/.well-known/jwks.json'
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
async function validateToken(token: string) {
|
|
221
|
+
try {
|
|
222
|
+
// Decode without verifying to get the kid
|
|
223
|
+
const decoded = jwt.decode(token, { complete: true });
|
|
224
|
+
if (!decoded || !decoded.header.kid) {
|
|
225
|
+
throw new Error('Invalid token: missing kid');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get the public key for this kid
|
|
229
|
+
const key = await jwksClient.getSigningKey(decoded.header.kid);
|
|
230
|
+
const publicKey = key.getPublicKey();
|
|
231
|
+
|
|
232
|
+
// Verify and decode the token
|
|
233
|
+
const verified = jwt.verify(token, publicKey, {
|
|
234
|
+
algorithms: ['RS256']
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return verified; // Returns the decoded claims
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error('Token validation failed:', error);
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Usage
|
|
245
|
+
const claims = await validateToken(req.headers.authorization.split(' ')[1]);
|
|
246
|
+
console.log(claims.email, claims.org, claims.service);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Other Languages
|
|
250
|
+
|
|
251
|
+
The same approach works in any language:
|
|
252
|
+
|
|
253
|
+
- **Python**: Use `PyJWT` with `python-jose` or `jwcrypto`
|
|
254
|
+
- **Go**: Use `golang-jwt/jwt` with JWKS support
|
|
255
|
+
- **Java**: Use `java-jwt` or Spring Security with JWKS
|
|
256
|
+
- **Ruby**: Use `jwt` gem with `jwks-ruby`
|
|
257
|
+
|
|
258
|
+
The key steps are always the same:
|
|
259
|
+
1. Fetch JWKS from `/.well-known/jwks.json`
|
|
260
|
+
2. Extract `kid` from JWT header
|
|
261
|
+
3. Find matching key in JWKS
|
|
262
|
+
4. Verify signature using the public key
|
|
263
|
+
5. Validate token claims (especially `exp`)
|
|
264
|
+
|
|
148
265
|
## API Reference
|
|
149
266
|
|
|
150
|
-
### Analytics
|
|
267
|
+
### Analytics (`sso.analytics`)
|
|
151
268
|
|
|
152
|
-
|
|
269
|
+
Provides login tracking and metrics for a specific organization.
|
|
153
270
|
|
|
154
271
|
```typescript
|
|
155
272
|
// Get login trends over time
|
|
@@ -159,7 +276,19 @@ const trends = await sso.analytics.getLoginTrends('acme-corp', {
|
|
|
159
276
|
});
|
|
160
277
|
```
|
|
161
278
|
|
|
162
|
-
###
|
|
279
|
+
### Authentication (`sso.auth`)
|
|
280
|
+
|
|
281
|
+
Handles all authentication flows, including OAuth, device flow, and token management.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// Get a fresh OAuth token for an external provider (e.g., GitHub)
|
|
285
|
+
const githubToken = await sso.auth.getProviderToken('github');
|
|
286
|
+
// Use githubToken.access_token to make GitHub API calls
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Organizations (`sso.organizations`)
|
|
290
|
+
|
|
291
|
+
Manages organizations, members, and BYOO credentials.
|
|
163
292
|
|
|
164
293
|
```typescript
|
|
165
294
|
// Create organization (public endpoint)
|
|
@@ -176,7 +305,7 @@ await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
|
|
|
176
305
|
});
|
|
177
306
|
```
|
|
178
307
|
|
|
179
|
-
|
|
308
|
+
#### End-User Management (`sso.organizations.endUsers`)
|
|
180
309
|
|
|
181
310
|
Manage your organization's customers (end-users with subscriptions).
|
|
182
311
|
|
|
@@ -191,7 +320,9 @@ const endUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
|
191
320
|
await sso.organizations.endUsers.revokeSessions('acme-corp', 'user-id-123');
|
|
192
321
|
```
|
|
193
322
|
|
|
194
|
-
### Services & Plans
|
|
323
|
+
### Services & Plans (`sso.services`)
|
|
324
|
+
|
|
325
|
+
Manages the applications (services) and subscription plans for an organization.
|
|
195
326
|
|
|
196
327
|
```typescript
|
|
197
328
|
// Create a service
|
|
@@ -211,9 +342,9 @@ await sso.services.plans.create('acme-corp', 'main-app', {
|
|
|
211
342
|
});
|
|
212
343
|
```
|
|
213
344
|
|
|
214
|
-
### User Profile & Identities
|
|
345
|
+
### User Profile & Identities (`sso.user`)
|
|
215
346
|
|
|
216
|
-
|
|
347
|
+
Manages the authenticated user's own profile and linked social accounts.
|
|
217
348
|
|
|
218
349
|
```typescript
|
|
219
350
|
// Get profile
|
|
@@ -227,7 +358,22 @@ window.location.href = authorization_url;
|
|
|
227
358
|
await sso.user.identities.unlink('github');
|
|
228
359
|
```
|
|
229
360
|
|
|
230
|
-
###
|
|
361
|
+
### Invitations (`sso.invitations`)
|
|
362
|
+
|
|
363
|
+
Manages team invitations for an organization.
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Create and send an invitation
|
|
367
|
+
await sso.invitations.create('acme-corp', {
|
|
368
|
+
email: 'new-dev@example.com',
|
|
369
|
+
role: 'member'
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// List invitations for the current user
|
|
373
|
+
const myInvites = await sso.invitations.listForUser();
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Platform Administration (`sso.platform`)
|
|
231
377
|
|
|
232
378
|
Platform owner methods require a Platform Owner JWT.
|
|
233
379
|
|
|
@@ -241,8 +387,14 @@ const pendingOrgs = await sso.platform.organizations.list({
|
|
|
241
387
|
await sso.platform.organizations.approve('org-id-123', {
|
|
242
388
|
tier_id: 'tier-starter'
|
|
243
389
|
});
|
|
390
|
+
```
|
|
244
391
|
|
|
245
|
-
|
|
392
|
+
#### Platform Analytics (`sso.platform.analytics`)
|
|
393
|
+
|
|
394
|
+
Provides platform-wide metrics for platform owners.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// Get platform-wide analytics overview
|
|
246
398
|
const overview = await sso.platform.analytics.getOverview();
|
|
247
399
|
console.log(`Total Users: ${overview.total_users}`);
|
|
248
400
|
```
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED