@drmhse/sso-sdk 0.2.2 → 0.2.4
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 +131 -4
- package/dist/index.d.mts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +14 -5
- package/dist/index.mjs +14 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,122 @@ localStorage.removeItem('sso_token');
|
|
|
146
146
|
localStorage.removeItem('sso_refresh_token');
|
|
147
147
|
```
|
|
148
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
|
+
|
|
149
265
|
## API Reference
|
|
150
266
|
|
|
151
267
|
### Analytics (`sso.analytics`)
|
|
@@ -191,16 +307,27 @@ await sso.organizations.oauthCredentials.set('acme-corp', 'github', {
|
|
|
191
307
|
|
|
192
308
|
#### End-User Management (`sso.organizations.endUsers`)
|
|
193
309
|
|
|
194
|
-
Manage your organization's customers (end-users
|
|
310
|
+
Manage your organization's customers (end-users who have logged in or have subscriptions).
|
|
195
311
|
|
|
196
312
|
```typescript
|
|
197
|
-
// List all end-users
|
|
198
|
-
const
|
|
313
|
+
// List all end-users across all services
|
|
314
|
+
const allUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
199
315
|
page: 1,
|
|
200
316
|
limit: 20
|
|
201
317
|
});
|
|
202
318
|
|
|
203
|
-
//
|
|
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
|
+
});
|
|
325
|
+
|
|
326
|
+
// Get detailed information about a specific end-user
|
|
327
|
+
const user = await sso.organizations.endUsers.get('acme-corp', 'user-id-123');
|
|
328
|
+
console.log(`Active sessions: ${user.session_count}`);
|
|
329
|
+
|
|
330
|
+
// Revoke all active sessions for a specific end-user (force logout)
|
|
204
331
|
await sso.organizations.endUsers.revokeSessions('acme-corp', 'user-id-123');
|
|
205
332
|
```
|
|
206
333
|
|
package/dist/index.d.mts
CHANGED
|
@@ -802,6 +802,10 @@ interface EndUserDetailResponse {
|
|
|
802
802
|
* List end-users query params
|
|
803
803
|
*/
|
|
804
804
|
interface ListEndUsersParams extends PaginationParams {
|
|
805
|
+
/**
|
|
806
|
+
* Optional service slug to filter users by a specific service
|
|
807
|
+
*/
|
|
808
|
+
service_slug?: string;
|
|
805
809
|
}
|
|
806
810
|
/**
|
|
807
811
|
* Revoke sessions response
|
|
@@ -1314,19 +1318,28 @@ declare class OrganizationsModule {
|
|
|
1314
1318
|
endUsers: {
|
|
1315
1319
|
/**
|
|
1316
1320
|
* List all end-users for an organization.
|
|
1317
|
-
*
|
|
1321
|
+
* Returns users who have identities (logged in) or subscriptions for the organization's services.
|
|
1318
1322
|
*
|
|
1319
1323
|
* @param orgSlug Organization slug
|
|
1320
|
-
* @param params Optional query parameters for pagination
|
|
1321
|
-
* @
|
|
1324
|
+
* @param params Optional query parameters for pagination and filtering
|
|
1325
|
+
* @param params.service_slug Optional service slug to filter users by a specific service
|
|
1326
|
+
* @returns Paginated list of end-users with their subscriptions and identities
|
|
1322
1327
|
*
|
|
1323
1328
|
* @example
|
|
1324
1329
|
* ```typescript
|
|
1325
|
-
*
|
|
1330
|
+
* // List all end-users across all services
|
|
1331
|
+
* const allUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
1332
|
+
* page: 1,
|
|
1333
|
+
* limit: 20
|
|
1334
|
+
* });
|
|
1335
|
+
*
|
|
1336
|
+
* // Filter by specific service
|
|
1337
|
+
* const serviceUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
1338
|
+
* service_slug: 'my-app',
|
|
1326
1339
|
* page: 1,
|
|
1327
1340
|
* limit: 20
|
|
1328
1341
|
* });
|
|
1329
|
-
* console.log(`Total end-users: ${
|
|
1342
|
+
* console.log(`Total end-users: ${allUsers.total}`);
|
|
1330
1343
|
* ```
|
|
1331
1344
|
*/
|
|
1332
1345
|
list: (orgSlug: string, params?: ListEndUsersParams) => Promise<EndUserListResponse>;
|
package/dist/index.d.ts
CHANGED
|
@@ -802,6 +802,10 @@ interface EndUserDetailResponse {
|
|
|
802
802
|
* List end-users query params
|
|
803
803
|
*/
|
|
804
804
|
interface ListEndUsersParams extends PaginationParams {
|
|
805
|
+
/**
|
|
806
|
+
* Optional service slug to filter users by a specific service
|
|
807
|
+
*/
|
|
808
|
+
service_slug?: string;
|
|
805
809
|
}
|
|
806
810
|
/**
|
|
807
811
|
* Revoke sessions response
|
|
@@ -1314,19 +1318,28 @@ declare class OrganizationsModule {
|
|
|
1314
1318
|
endUsers: {
|
|
1315
1319
|
/**
|
|
1316
1320
|
* List all end-users for an organization.
|
|
1317
|
-
*
|
|
1321
|
+
* Returns users who have identities (logged in) or subscriptions for the organization's services.
|
|
1318
1322
|
*
|
|
1319
1323
|
* @param orgSlug Organization slug
|
|
1320
|
-
* @param params Optional query parameters for pagination
|
|
1321
|
-
* @
|
|
1324
|
+
* @param params Optional query parameters for pagination and filtering
|
|
1325
|
+
* @param params.service_slug Optional service slug to filter users by a specific service
|
|
1326
|
+
* @returns Paginated list of end-users with their subscriptions and identities
|
|
1322
1327
|
*
|
|
1323
1328
|
* @example
|
|
1324
1329
|
* ```typescript
|
|
1325
|
-
*
|
|
1330
|
+
* // List all end-users across all services
|
|
1331
|
+
* const allUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
1332
|
+
* page: 1,
|
|
1333
|
+
* limit: 20
|
|
1334
|
+
* });
|
|
1335
|
+
*
|
|
1336
|
+
* // Filter by specific service
|
|
1337
|
+
* const serviceUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
1338
|
+
* service_slug: 'my-app',
|
|
1326
1339
|
* page: 1,
|
|
1327
1340
|
* limit: 20
|
|
1328
1341
|
* });
|
|
1329
|
-
* console.log(`Total end-users: ${
|
|
1342
|
+
* console.log(`Total end-users: ${allUsers.total}`);
|
|
1330
1343
|
* ```
|
|
1331
1344
|
*/
|
|
1332
1345
|
list: (orgSlug: string, params?: ListEndUsersParams) => Promise<EndUserListResponse>;
|
package/dist/index.js
CHANGED
|
@@ -724,19 +724,28 @@ var OrganizationsModule = class {
|
|
|
724
724
|
this.endUsers = {
|
|
725
725
|
/**
|
|
726
726
|
* List all end-users for an organization.
|
|
727
|
-
*
|
|
727
|
+
* Returns users who have identities (logged in) or subscriptions for the organization's services.
|
|
728
728
|
*
|
|
729
729
|
* @param orgSlug Organization slug
|
|
730
|
-
* @param params Optional query parameters for pagination
|
|
731
|
-
* @
|
|
730
|
+
* @param params Optional query parameters for pagination and filtering
|
|
731
|
+
* @param params.service_slug Optional service slug to filter users by a specific service
|
|
732
|
+
* @returns Paginated list of end-users with their subscriptions and identities
|
|
732
733
|
*
|
|
733
734
|
* @example
|
|
734
735
|
* ```typescript
|
|
735
|
-
*
|
|
736
|
+
* // List all end-users across all services
|
|
737
|
+
* const allUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
736
738
|
* page: 1,
|
|
737
739
|
* limit: 20
|
|
738
740
|
* });
|
|
739
|
-
*
|
|
741
|
+
*
|
|
742
|
+
* // Filter by specific service
|
|
743
|
+
* const serviceUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
744
|
+
* service_slug: 'my-app',
|
|
745
|
+
* page: 1,
|
|
746
|
+
* limit: 20
|
|
747
|
+
* });
|
|
748
|
+
* console.log(`Total end-users: ${allUsers.total}`);
|
|
740
749
|
* ```
|
|
741
750
|
*/
|
|
742
751
|
list: async (orgSlug, params) => {
|
package/dist/index.mjs
CHANGED
|
@@ -691,19 +691,28 @@ var OrganizationsModule = class {
|
|
|
691
691
|
this.endUsers = {
|
|
692
692
|
/**
|
|
693
693
|
* List all end-users for an organization.
|
|
694
|
-
*
|
|
694
|
+
* Returns users who have identities (logged in) or subscriptions for the organization's services.
|
|
695
695
|
*
|
|
696
696
|
* @param orgSlug Organization slug
|
|
697
|
-
* @param params Optional query parameters for pagination
|
|
698
|
-
* @
|
|
697
|
+
* @param params Optional query parameters for pagination and filtering
|
|
698
|
+
* @param params.service_slug Optional service slug to filter users by a specific service
|
|
699
|
+
* @returns Paginated list of end-users with their subscriptions and identities
|
|
699
700
|
*
|
|
700
701
|
* @example
|
|
701
702
|
* ```typescript
|
|
702
|
-
*
|
|
703
|
+
* // List all end-users across all services
|
|
704
|
+
* const allUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
703
705
|
* page: 1,
|
|
704
706
|
* limit: 20
|
|
705
707
|
* });
|
|
706
|
-
*
|
|
708
|
+
*
|
|
709
|
+
* // Filter by specific service
|
|
710
|
+
* const serviceUsers = await sso.organizations.endUsers.list('acme-corp', {
|
|
711
|
+
* service_slug: 'my-app',
|
|
712
|
+
* page: 1,
|
|
713
|
+
* limit: 20
|
|
714
|
+
* });
|
|
715
|
+
* console.log(`Total end-users: ${allUsers.total}`);
|
|
707
716
|
* ```
|
|
708
717
|
*/
|
|
709
718
|
list: async (orgSlug, params) => {
|