@drmhse/sso-sdk 0.2.4 → 0.2.6
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 +215 -310
- package/dist/index.d.mts +1751 -145
- package/dist/index.d.ts +1751 -145
- package/dist/index.js +1570 -266
- package/dist/index.mjs +1569 -266
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
# SSO Platform SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@drmhse/sso-sdk)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A zero-dependency, strongly-typed TypeScript SDK for the multi-tenant SSO Platform API.
|
|
7
|
+
|
|
8
|
+
**📚 [View Full Documentation →](https://drmhse.com/docs/sso/)**
|
|
4
9
|
|
|
5
10
|
## Features
|
|
6
11
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
12
|
+
- ✅ **Zero Dependencies** - Built on native `fetch` API
|
|
13
|
+
- ✅ **Strongly Typed** - Complete TypeScript definitions
|
|
14
|
+
- ✅ **Framework Agnostic** - Works in any JavaScript environment
|
|
15
|
+
- ✅ **OAuth 2.0 Flows** - Support for GitHub, Google, Microsoft
|
|
16
|
+
- ✅ **Password Authentication** - Native email/password auth with MFA
|
|
17
|
+
- ✅ **Device Flow** - RFC 8628 for CLIs and headless apps
|
|
18
|
+
- ✅ **Multi-Factor Authentication** - TOTP-based 2FA with backup codes
|
|
19
|
+
- ✅ **Organization Management** - Multi-tenant with RBAC
|
|
20
|
+
- ✅ **Analytics & Audit Logs** - Track authentication and administrative actions
|
|
21
|
+
- ✅ **SAML 2.0** - Act as Identity Provider
|
|
13
22
|
|
|
14
23
|
## Installation
|
|
15
24
|
|
|
@@ -20,38 +29,37 @@ npm install @drmhse/sso-sdk
|
|
|
20
29
|
## Quick Start
|
|
21
30
|
|
|
22
31
|
```typescript
|
|
23
|
-
import { SsoClient
|
|
32
|
+
import { SsoClient } from '@drmhse/sso-sdk';
|
|
24
33
|
|
|
25
34
|
// Initialize the client
|
|
26
35
|
const sso = new SsoClient({
|
|
27
36
|
baseURL: 'https://sso.example.com',
|
|
28
|
-
token: localStorage.getItem('
|
|
37
|
+
token: localStorage.getItem('sso_access_token')
|
|
29
38
|
});
|
|
30
39
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Get user profile
|
|
35
|
-
const profile = await sso.user.getProfile();
|
|
36
|
-
console.log(profile.email);
|
|
40
|
+
// Get user profile
|
|
41
|
+
const profile = await sso.user.getProfile();
|
|
42
|
+
console.log(profile.email);
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} catch (error) {
|
|
42
|
-
if (error instanceof SsoApiError) {
|
|
43
|
-
console.error(`API Error: ${error.message} (${error.errorCode})`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
44
|
+
// List organizations
|
|
45
|
+
const orgs = await sso.organizations.list();
|
|
46
|
+
console.log(orgs);
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
**New to the SDK?** Start with the **[Getting Started Guide →](https://drmhse.com/docs/sso/sdk/getting-started)** for a step-by-step tutorial showing how to authenticate users from scratch, handle token refresh, and implement logout.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
**Essential Guides:**
|
|
52
|
+
- **[Authentication Flows](https://drmhse.com/docs/sso/sdk/guides/authentication-flows)** - OAuth, Device Flow, and Admin Login patterns
|
|
53
|
+
- **[Password Authentication](https://drmhse.com/docs/sso/sdk/guides/password-authentication)** - Registration, login, and password reset
|
|
54
|
+
- **[MFA Management](https://drmhse.com/docs/sso/sdk/guides/mfa-management)** - TOTP-based 2FA implementation
|
|
55
|
+
- **[Error Handling](https://drmhse.com/docs/sso/sdk/guides/error-handling)** - Best practices for handling API errors
|
|
56
|
+
|
|
57
|
+
## Authentication Examples
|
|
58
|
+
|
|
59
|
+
### OAuth Login
|
|
52
60
|
|
|
53
61
|
```typescript
|
|
54
|
-
//
|
|
62
|
+
// Redirect to OAuth provider
|
|
55
63
|
const loginUrl = sso.auth.getLoginUrl('github', {
|
|
56
64
|
org: 'acme-corp',
|
|
57
65
|
service: 'main-app',
|
|
@@ -59,393 +67,290 @@ const loginUrl = sso.auth.getLoginUrl('github', {
|
|
|
59
67
|
});
|
|
60
68
|
window.location.href = loginUrl;
|
|
61
69
|
|
|
62
|
-
//
|
|
70
|
+
// Handle callback
|
|
63
71
|
const params = new URLSearchParams(window.location.search);
|
|
64
72
|
const accessToken = params.get('access_token');
|
|
65
73
|
const refreshToken = params.get('refresh_token');
|
|
66
74
|
|
|
67
75
|
if (accessToken && refreshToken) {
|
|
68
76
|
sso.setAuthToken(accessToken);
|
|
69
|
-
localStorage.setItem('
|
|
77
|
+
localStorage.setItem('sso_access_token', accessToken);
|
|
70
78
|
localStorage.setItem('sso_refresh_token', refreshToken);
|
|
71
79
|
}
|
|
72
80
|
```
|
|
73
81
|
|
|
74
|
-
###
|
|
82
|
+
### Password Authentication
|
|
75
83
|
|
|
76
84
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
// Register new user
|
|
86
|
+
await sso.auth.register({
|
|
87
|
+
email: 'user@example.com',
|
|
88
|
+
password: 'SecurePass123!',
|
|
89
|
+
org_slug: 'acme-corp'
|
|
79
90
|
});
|
|
80
|
-
|
|
91
|
+
|
|
92
|
+
// Login with password
|
|
93
|
+
const tokens = await sso.auth.login({
|
|
94
|
+
email: 'user@example.com',
|
|
95
|
+
password: 'SecurePass123!'
|
|
96
|
+
});
|
|
97
|
+
sso.setAuthToken(tokens.access_token);
|
|
98
|
+
|
|
99
|
+
// Enable MFA
|
|
100
|
+
const mfaSetup = await sso.user.mfa.setup();
|
|
101
|
+
console.log(mfaSetup.qr_code); // Display QR code to user
|
|
102
|
+
|
|
103
|
+
// Verify and enable
|
|
104
|
+
await sso.user.mfa.verify('123456'); // TOTP code from authenticator app
|
|
81
105
|
```
|
|
82
106
|
|
|
83
107
|
### Device Flow (for CLIs)
|
|
84
108
|
|
|
85
|
-
This flow involves both the CLI and a web browser.
|
|
86
|
-
|
|
87
109
|
```typescript
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
// 1. Request device code
|
|
110
|
+
// In your CLI application
|
|
91
111
|
const deviceAuth = await sso.auth.deviceCode.request({
|
|
92
|
-
client_id: 'cli-client
|
|
112
|
+
client_id: 'cli-client',
|
|
93
113
|
org: 'acme-corp',
|
|
94
|
-
service: '
|
|
114
|
+
service: 'cli-tool'
|
|
95
115
|
});
|
|
96
116
|
|
|
97
117
|
console.log(`Visit: ${deviceAuth.verification_uri}`);
|
|
98
118
|
console.log(`Enter code: ${deviceAuth.user_code}`);
|
|
99
119
|
|
|
100
|
-
//
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// --- In your Web Application at /activate ---
|
|
108
|
-
|
|
109
|
-
// 2. After user enters the code, verify it to get context
|
|
110
|
-
const context = await sso.auth.deviceCode.verify(userEnteredCode);
|
|
120
|
+
// Poll for token
|
|
121
|
+
const interval = setInterval(async () => {
|
|
122
|
+
try {
|
|
123
|
+
const tokens = await sso.auth.deviceCode.exchangeToken({
|
|
124
|
+
device_code: deviceAuth.device_code,
|
|
125
|
+
client_id: 'cli-client'
|
|
126
|
+
});
|
|
111
127
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
119
|
-
window.location.href = loginUrl; // User logs in, authorizing the device
|
|
128
|
+
sso.setAuthToken(tokens.access_token);
|
|
129
|
+
clearInterval(interval);
|
|
130
|
+
console.log('✓ Authentication successful!');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// Continue polling...
|
|
133
|
+
}
|
|
134
|
+
}, deviceAuth.interval * 1000);
|
|
120
135
|
```
|
|
121
136
|
|
|
122
|
-
###
|
|
123
|
-
|
|
124
|
-
Renew an expired access token using a refresh token. This uses token rotation for enhanced security.
|
|
137
|
+
### Token Refresh
|
|
125
138
|
|
|
126
139
|
```typescript
|
|
127
140
|
try {
|
|
128
141
|
const tokens = await sso.auth.refreshToken(storedRefreshToken);
|
|
129
|
-
|
|
130
|
-
// Update tokens in your application state and storage
|
|
142
|
+
|
|
131
143
|
sso.setAuthToken(tokens.access_token);
|
|
132
|
-
localStorage.setItem('
|
|
144
|
+
localStorage.setItem('sso_access_token', tokens.access_token);
|
|
133
145
|
localStorage.setItem('sso_refresh_token', tokens.refresh_token);
|
|
134
146
|
} catch (error) {
|
|
135
|
-
// Refresh failed
|
|
136
|
-
console.error(
|
|
147
|
+
// Refresh failed - redirect to login
|
|
148
|
+
console.error('Token refresh failed:', error);
|
|
137
149
|
}
|
|
138
150
|
```
|
|
139
151
|
|
|
140
|
-
|
|
152
|
+
## Organization Management
|
|
141
153
|
|
|
142
154
|
```typescript
|
|
143
|
-
|
|
144
|
-
sso.
|
|
145
|
-
|
|
146
|
-
|
|
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'
|
|
155
|
+
// Create organization
|
|
156
|
+
const org = await sso.organizations.createPublic({
|
|
157
|
+
name: 'Acme Corp',
|
|
158
|
+
slug: 'acme-corp'
|
|
174
159
|
});
|
|
175
160
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
const signingKey = key.getPublicKey();
|
|
183
|
-
callback(null, signingKey);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
161
|
+
// Configure custom OAuth (BYOO - Bring Your Own OAuth)
|
|
162
|
+
await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
|
|
163
|
+
client_id: 'your-github-client-id',
|
|
164
|
+
client_secret: 'your-github-client-secret'
|
|
165
|
+
});
|
|
186
166
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
}
|
|
167
|
+
// Invite team members
|
|
168
|
+
await sso.organizations.invitations.create('acme-corp', {
|
|
169
|
+
email: 'member@acme.com',
|
|
170
|
+
role: 'admin'
|
|
198
171
|
});
|
|
199
172
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
173
|
+
// Configure SMTP for transactional emails
|
|
174
|
+
await sso.organizations.setSmtp('acme-corp', {
|
|
175
|
+
host: 'smtp.gmail.com',
|
|
176
|
+
port: 587,
|
|
177
|
+
username: 'noreply@acme.com',
|
|
178
|
+
password: 'app-password',
|
|
179
|
+
from_email: 'noreply@acme.com',
|
|
180
|
+
from_name: 'Acme Corp'
|
|
205
181
|
});
|
|
206
182
|
```
|
|
207
183
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
If you prefer to validate manually without middleware:
|
|
184
|
+
## Services & API Keys
|
|
211
185
|
|
|
212
186
|
```typescript
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
187
|
+
// Create a service
|
|
188
|
+
const service = await sso.services.create('acme-corp', {
|
|
189
|
+
name: 'Main Application',
|
|
190
|
+
slug: 'main-app',
|
|
191
|
+
redirect_uris: ['https://app.acme.com/callback']
|
|
218
192
|
});
|
|
219
193
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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();
|
|
194
|
+
// Create API key for service-to-service auth
|
|
195
|
+
const apiKey = await sso.services.apiKeys.create('acme-corp', 'main-app', {
|
|
196
|
+
name: 'Production Backend',
|
|
197
|
+
expires_at: '2026-01-01T00:00:00Z'
|
|
198
|
+
});
|
|
231
199
|
|
|
232
|
-
|
|
233
|
-
const verified = jwt.verify(token, publicKey, {
|
|
234
|
-
algorithms: ['RS256']
|
|
235
|
-
});
|
|
200
|
+
console.log('API Key (save this):', apiKey.key);
|
|
236
201
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Usage
|
|
245
|
-
const claims = await validateToken(req.headers.authorization.split(' ')[1]);
|
|
246
|
-
console.log(claims.email, claims.org, claims.service);
|
|
202
|
+
// Use API key for backend authentication
|
|
203
|
+
const backendClient = new SsoClient({
|
|
204
|
+
baseURL: 'https://sso.example.com',
|
|
205
|
+
apiKey: apiKey.key
|
|
206
|
+
});
|
|
247
207
|
```
|
|
248
208
|
|
|
249
|
-
|
|
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
|
-
|
|
265
|
-
## API Reference
|
|
266
|
-
|
|
267
|
-
### Analytics (`sso.analytics`)
|
|
268
|
-
|
|
269
|
-
Provides login tracking and metrics for a specific organization.
|
|
209
|
+
## Analytics
|
|
270
210
|
|
|
271
211
|
```typescript
|
|
272
|
-
// Get login trends
|
|
212
|
+
// Get login trends
|
|
273
213
|
const trends = await sso.analytics.getLoginTrends('acme-corp', {
|
|
274
214
|
start_date: '2025-01-01',
|
|
275
215
|
end_date: '2025-01-31'
|
|
276
216
|
});
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Authentication (`sso.auth`)
|
|
280
|
-
|
|
281
|
-
Handles all authentication flows, including OAuth, device flow, and token management.
|
|
282
217
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// Use githubToken.access_token to make GitHub API calls
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### Organizations (`sso.organizations`)
|
|
218
|
+
// Get provider distribution
|
|
219
|
+
const byProvider = await sso.analytics.getLoginsByProvider('acme-corp');
|
|
220
|
+
console.log(byProvider); // [{ provider: 'github', count: 1523 }, ...]
|
|
290
221
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// Create organization (public endpoint)
|
|
295
|
-
const org = await sso.organizations.createPublic({
|
|
296
|
-
slug: 'acme-corp',
|
|
297
|
-
name: 'Acme Corporation',
|
|
298
|
-
owner_email: 'founder@acme.com'
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// BYOO: Set custom OAuth credentials
|
|
302
|
-
await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
|
|
303
|
-
client_id: 'Iv1.abc123',
|
|
304
|
-
client_secret: 'secret-value'
|
|
222
|
+
// Get recent activity
|
|
223
|
+
const recentLogins = await sso.analytics.getRecentLogins('acme-corp', {
|
|
224
|
+
limit: 20
|
|
305
225
|
});
|
|
306
226
|
```
|
|
307
227
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
Manage your organization's customers (end-users who have logged in or have subscriptions).
|
|
228
|
+
## Error Handling
|
|
311
229
|
|
|
312
230
|
```typescript
|
|
313
|
-
|
|
314
|
-
const allUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
315
|
-
page: 1,
|
|
316
|
-
limit: 20
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Filter end-users by a specific service
|
|
320
|
-
const serviceUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
321
|
-
service_slug: 'main-app',
|
|
322
|
-
page: 1,
|
|
323
|
-
limit: 20
|
|
324
|
-
});
|
|
231
|
+
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';
|
|
325
232
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
233
|
+
try {
|
|
234
|
+
await sso.user.getProfile();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error instanceof SsoApiError) {
|
|
237
|
+
console.error(`API Error: ${error.message}`);
|
|
238
|
+
console.error(`Status: ${error.status}`);
|
|
239
|
+
console.error(`Code: ${error.errorCode}`);
|
|
329
240
|
|
|
330
|
-
|
|
331
|
-
|
|
241
|
+
if (error.status === 401) {
|
|
242
|
+
// Token expired - refresh or re-authenticate
|
|
243
|
+
} else if (error.status === 403) {
|
|
244
|
+
// Forbidden - insufficient permissions
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
console.error('Unexpected error:', error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
332
250
|
```
|
|
333
251
|
|
|
334
|
-
|
|
252
|
+
## Platform Administration
|
|
335
253
|
|
|
336
|
-
|
|
254
|
+
For platform owners managing the entire SSO system:
|
|
337
255
|
|
|
338
256
|
```typescript
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
service_type: 'web',
|
|
344
|
-
redirect_uris: ['https://app.acme.com/callback']
|
|
257
|
+
// Approve pending organization
|
|
258
|
+
await sso.platform.organizations.approve('org-id', {
|
|
259
|
+
tier: 'professional',
|
|
260
|
+
reason: 'Verified enterprise customer'
|
|
345
261
|
});
|
|
346
262
|
|
|
347
|
-
//
|
|
348
|
-
await sso.
|
|
349
|
-
|
|
350
|
-
price_cents: 2999, // Note: price is in cents
|
|
351
|
-
currency: 'usd',
|
|
352
|
-
features: ['api-access', 'priority-support']
|
|
263
|
+
// Promote user to platform owner
|
|
264
|
+
await sso.platform.promoteOwner({
|
|
265
|
+
email: 'admin@example.com'
|
|
353
266
|
});
|
|
267
|
+
|
|
268
|
+
// Get platform analytics
|
|
269
|
+
const overview = await sso.platform.analytics.getOverview();
|
|
270
|
+
console.log(overview); // { total_users, total_orgs, total_logins, ... }
|
|
271
|
+
|
|
272
|
+
// Search users across all organizations
|
|
273
|
+
const users = await sso.platform.users.search('user@example.com');
|
|
354
274
|
```
|
|
355
275
|
|
|
356
|
-
|
|
276
|
+
## TypeScript Support
|
|
357
277
|
|
|
358
|
-
|
|
278
|
+
The SDK is written in TypeScript and includes complete type definitions:
|
|
359
279
|
|
|
360
280
|
```typescript
|
|
361
|
-
|
|
362
|
-
|
|
281
|
+
import type {
|
|
282
|
+
User,
|
|
283
|
+
Organization,
|
|
284
|
+
Service,
|
|
285
|
+
LoginTrendPoint,
|
|
286
|
+
RefreshTokenResponse,
|
|
287
|
+
CreateServicePayload,
|
|
288
|
+
UpdateServicePayload,
|
|
289
|
+
LoginPayload,
|
|
290
|
+
RegisterPayload,
|
|
291
|
+
SsoApiError
|
|
292
|
+
} from '@drmhse/sso-sdk';
|
|
363
293
|
|
|
364
|
-
//
|
|
365
|
-
const
|
|
366
|
-
|
|
294
|
+
// Example using types
|
|
295
|
+
const createService = async (payload: CreateServicePayload): Promise<Service> => {
|
|
296
|
+
return await sso.services.create('org-slug', payload);
|
|
297
|
+
};
|
|
367
298
|
|
|
368
|
-
|
|
369
|
-
await sso.
|
|
299
|
+
const login = async (credentials: LoginPayload): Promise<RefreshTokenResponse> => {
|
|
300
|
+
return await sso.auth.login(credentials);
|
|
301
|
+
};
|
|
370
302
|
```
|
|
371
303
|
|
|
372
|
-
|
|
304
|
+
## Validating JWTs in Your Backend
|
|
373
305
|
|
|
374
|
-
|
|
306
|
+
The SSO platform uses RS256 (asymmetric) JWT signing. Your backend can validate tokens without sharing secrets:
|
|
375
307
|
|
|
376
308
|
```typescript
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
});
|
|
309
|
+
// Fetch JWKS from the SSO platform
|
|
310
|
+
const jwksUrl = 'https://sso.example.com/.well-known/jwks.json';
|
|
311
|
+
const response = await fetch(jwksUrl);
|
|
312
|
+
const jwks = await response.json();
|
|
382
313
|
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
### Platform Administration (`sso.platform`)
|
|
388
|
-
|
|
389
|
-
Platform owner methods require a Platform Owner JWT.
|
|
314
|
+
// Use a JWT library to verify tokens
|
|
315
|
+
import jwt from 'jsonwebtoken';
|
|
316
|
+
import jwksClient from 'jwks-rsa';
|
|
390
317
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
status: 'pending'
|
|
395
|
-
});
|
|
318
|
+
const client = jwksClient({ jwksUri: jwksUrl });
|
|
319
|
+
const key = await client.getSigningKey(header.kid);
|
|
320
|
+
const publicKey = key.getPublicKey();
|
|
396
321
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
tier_id: 'tier-starter'
|
|
322
|
+
const decoded = jwt.verify(token, publicKey, {
|
|
323
|
+
algorithms: ['RS256']
|
|
400
324
|
});
|
|
401
325
|
```
|
|
402
326
|
|
|
403
|
-
|
|
327
|
+
**📚 [See Backend Validation Guide →](https://drmhse.com/docs/sso/api/concepts/token-validation)**
|
|
404
328
|
|
|
405
|
-
|
|
329
|
+
## Documentation
|
|
406
330
|
|
|
407
|
-
|
|
408
|
-
// Get platform-wide analytics overview
|
|
409
|
-
const overview = await sso.platform.analytics.getOverview();
|
|
410
|
-
console.log(`Total Users: ${overview.total_users}`);
|
|
411
|
-
```
|
|
331
|
+
**[Complete documentation is available at drmhse.com/docs/sso](https://drmhse.com/docs/sso/)**
|
|
412
332
|
|
|
413
|
-
|
|
333
|
+
### Key Documentation Pages
|
|
414
334
|
|
|
415
|
-
|
|
335
|
+
- **[Getting Started](https://drmhse.com/docs/sso/sdk/getting-started)** - Installation and setup
|
|
336
|
+
- **[Authentication Flows](https://drmhse.com/docs/sso/sdk/guides/authentication-flows)** - OAuth, Device Flow, Admin Login
|
|
337
|
+
- **[Password Authentication](https://drmhse.com/docs/sso/sdk/guides/password-authentication)** - Register, Login, Reset Password
|
|
338
|
+
- **[MFA Management](https://drmhse.com/docs/sso/sdk/guides/mfa-management)** - TOTP setup and verification
|
|
339
|
+
- **[SDK Reference](https://drmhse.com/docs/sso/sdk/reference)** - Complete API reference
|
|
340
|
+
- **[API Reference](https://drmhse.com/docs/sso/api/reference)** - Backend API documentation
|
|
416
341
|
|
|
417
|
-
|
|
418
|
-
import { SsoApiError } from '@drmhse/sso-sdk';
|
|
342
|
+
## Requirements
|
|
419
343
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (error instanceof SsoApiError) {
|
|
424
|
-
console.error(`Error ${error.statusCode}: ${error.message}`);
|
|
425
|
-
console.error(`Code: ${error.errorCode}`);
|
|
344
|
+
- **Node.js:** 18+ (for native fetch support)
|
|
345
|
+
- **Browsers:** All modern browsers with fetch support
|
|
346
|
+
- **TypeScript:** 4.5+ (optional, but recommended)
|
|
426
347
|
|
|
427
|
-
|
|
428
|
-
// Redirect to login
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
## TypeScript
|
|
348
|
+
## License
|
|
435
349
|
|
|
436
|
-
|
|
350
|
+
MIT © [DRM HSE](https://github.com/drmhse)
|
|
437
351
|
|
|
438
|
-
|
|
439
|
-
import type {
|
|
440
|
-
Organization,
|
|
441
|
-
Service,
|
|
442
|
-
User,
|
|
443
|
-
JwtClaims,
|
|
444
|
-
SsoApiError,
|
|
445
|
-
// ... and many more types
|
|
446
|
-
} from '@drmhse/sso-sdk';
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
## License
|
|
352
|
+
## Support
|
|
450
353
|
|
|
451
|
-
|
|
354
|
+
- **Documentation:** [drmhse.com/docs/sso](https://drmhse.com/docs/sso/)
|
|
355
|
+
- **Issues:** [GitHub Issues](https://github.com/drmhse/sso/issues)
|
|
356
|
+
- **Email:** [info@drmhse.com](mailto:info@drmhse.com)
|