@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 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 with subscriptions).
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 for an organization
198
- const endUsers = await sso.organizations.endUsers.list('acme-corp', {
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
- // Revoke all active sessions for a specific end-user
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
- * End-users are customers who have subscriptions to the organization's services.
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
- * @returns Paginated list of end-users with their subscriptions
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
- * const endUsers = await sso.organizations.endUsers.list('acme-corp', {
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: ${endUsers.total}`);
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
- * End-users are customers who have subscriptions to the organization's services.
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
- * @returns Paginated list of end-users with their subscriptions
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
- * const endUsers = await sso.organizations.endUsers.list('acme-corp', {
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: ${endUsers.total}`);
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
- * End-users are customers who have subscriptions to the organization's services.
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
- * @returns Paginated list of end-users with their subscriptions
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
- * const endUsers = await sso.organizations.endUsers.list('acme-corp', {
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
- * console.log(`Total end-users: ${endUsers.total}`);
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
- * End-users are customers who have subscriptions to the organization's services.
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
- * @returns Paginated list of end-users with their subscriptions
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
- * const endUsers = await sso.organizations.endUsers.list('acme-corp', {
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
- * console.log(`Total end-users: ${endUsers.total}`);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drmhse/sso-sdk",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Zero-dependency TypeScript SDK for the multi-tenant SSO Platform API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",