@drmhse/sso-sdk 0.2.3 → 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 +216 -300
- package/dist/index.d.mts +1767 -148
- package/dist/index.d.ts +1767 -148
- package/dist/index.js +1569 -256
- package/dist/index.mjs +1568 -256
- 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,382 +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
|
-
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
await sso.auth.logout();
|
|
144
|
-
sso.setAuthToken(null);
|
|
145
|
-
localStorage.removeItem('sso_token');
|
|
146
|
-
localStorage.removeItem('sso_refresh_token');
|
|
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:
|
|
152
|
+
## Organization Management
|
|
163
153
|
|
|
164
154
|
```typescript
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
217
|
|
|
281
|
-
|
|
218
|
+
// Get provider distribution
|
|
219
|
+
const byProvider = await sso.analytics.getLoginsByProvider('acme-corp');
|
|
220
|
+
console.log(byProvider); // [{ provider: 'github', count: 1523 }, ...]
|
|
282
221
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// Use githubToken.access_token to make GitHub API calls
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### Organizations (`sso.organizations`)
|
|
290
|
-
|
|
291
|
-
Manages organizations, members, and BYOO credentials.
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
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 with subscriptions).
|
|
228
|
+
## Error Handling
|
|
311
229
|
|
|
312
230
|
```typescript
|
|
313
|
-
|
|
314
|
-
const endUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
315
|
-
page: 1,
|
|
316
|
-
limit: 20
|
|
317
|
-
});
|
|
231
|
+
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';
|
|
318
232
|
|
|
319
|
-
|
|
320
|
-
await sso.
|
|
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}`);
|
|
240
|
+
|
|
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
|
+
}
|
|
321
250
|
```
|
|
322
251
|
|
|
323
|
-
|
|
252
|
+
## Platform Administration
|
|
324
253
|
|
|
325
|
-
|
|
254
|
+
For platform owners managing the entire SSO system:
|
|
326
255
|
|
|
327
256
|
```typescript
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
service_type: 'web',
|
|
333
|
-
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'
|
|
334
261
|
});
|
|
335
262
|
|
|
336
|
-
//
|
|
337
|
-
await sso.
|
|
338
|
-
|
|
339
|
-
price_cents: 2999, // Note: price is in cents
|
|
340
|
-
currency: 'usd',
|
|
341
|
-
features: ['api-access', 'priority-support']
|
|
263
|
+
// Promote user to platform owner
|
|
264
|
+
await sso.platform.promoteOwner({
|
|
265
|
+
email: 'admin@example.com'
|
|
342
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');
|
|
343
274
|
```
|
|
344
275
|
|
|
345
|
-
|
|
276
|
+
## TypeScript Support
|
|
346
277
|
|
|
347
|
-
|
|
278
|
+
The SDK is written in TypeScript and includes complete type definitions:
|
|
348
279
|
|
|
349
280
|
```typescript
|
|
350
|
-
|
|
351
|
-
|
|
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';
|
|
352
293
|
|
|
353
|
-
//
|
|
354
|
-
const
|
|
355
|
-
|
|
294
|
+
// Example using types
|
|
295
|
+
const createService = async (payload: CreateServicePayload): Promise<Service> => {
|
|
296
|
+
return await sso.services.create('org-slug', payload);
|
|
297
|
+
};
|
|
356
298
|
|
|
357
|
-
|
|
358
|
-
await sso.
|
|
299
|
+
const login = async (credentials: LoginPayload): Promise<RefreshTokenResponse> => {
|
|
300
|
+
return await sso.auth.login(credentials);
|
|
301
|
+
};
|
|
359
302
|
```
|
|
360
303
|
|
|
361
|
-
|
|
304
|
+
## Validating JWTs in Your Backend
|
|
362
305
|
|
|
363
|
-
|
|
306
|
+
The SSO platform uses RS256 (asymmetric) JWT signing. Your backend can validate tokens without sharing secrets:
|
|
364
307
|
|
|
365
308
|
```typescript
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
});
|
|
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();
|
|
371
313
|
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
### Platform Administration (`sso.platform`)
|
|
377
|
-
|
|
378
|
-
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';
|
|
379
317
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
status: 'pending'
|
|
384
|
-
});
|
|
318
|
+
const client = jwksClient({ jwksUri: jwksUrl });
|
|
319
|
+
const key = await client.getSigningKey(header.kid);
|
|
320
|
+
const publicKey = key.getPublicKey();
|
|
385
321
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
tier_id: 'tier-starter'
|
|
322
|
+
const decoded = jwt.verify(token, publicKey, {
|
|
323
|
+
algorithms: ['RS256']
|
|
389
324
|
});
|
|
390
325
|
```
|
|
391
326
|
|
|
392
|
-
|
|
327
|
+
**📚 [See Backend Validation Guide →](https://drmhse.com/docs/sso/api/concepts/token-validation)**
|
|
393
328
|
|
|
394
|
-
|
|
329
|
+
## Documentation
|
|
395
330
|
|
|
396
|
-
|
|
397
|
-
// Get platform-wide analytics overview
|
|
398
|
-
const overview = await sso.platform.analytics.getOverview();
|
|
399
|
-
console.log(`Total Users: ${overview.total_users}`);
|
|
400
|
-
```
|
|
331
|
+
**[Complete documentation is available at drmhse.com/docs/sso](https://drmhse.com/docs/sso/)**
|
|
401
332
|
|
|
402
|
-
|
|
333
|
+
### Key Documentation Pages
|
|
403
334
|
|
|
404
|
-
|
|
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
|
|
405
341
|
|
|
406
|
-
|
|
407
|
-
import { SsoApiError } from '@drmhse/sso-sdk';
|
|
342
|
+
## Requirements
|
|
408
343
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (error instanceof SsoApiError) {
|
|
413
|
-
console.error(`Error ${error.statusCode}: ${error.message}`);
|
|
414
|
-
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)
|
|
415
347
|
|
|
416
|
-
|
|
417
|
-
// Redirect to login
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
## TypeScript
|
|
348
|
+
## License
|
|
424
349
|
|
|
425
|
-
|
|
350
|
+
MIT © [DRM HSE](https://github.com/drmhse)
|
|
426
351
|
|
|
427
|
-
|
|
428
|
-
import type {
|
|
429
|
-
Organization,
|
|
430
|
-
Service,
|
|
431
|
-
User,
|
|
432
|
-
JwtClaims,
|
|
433
|
-
SsoApiError,
|
|
434
|
-
// ... and many more types
|
|
435
|
-
} from '@drmhse/sso-sdk';
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
## License
|
|
352
|
+
## Support
|
|
439
353
|
|
|
440
|
-
|
|
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)
|