@bigso/auth-sdk 0.5.3 → 0.5.5

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
@@ -10,6 +10,7 @@ SDK oficial de autenticación para Bigso SSO v2. Flujo basado en JWT Bearer toke
10
10
  - **JWS verification** en frontend con JWKS remoto
11
11
  - **3 entry points**: Browser, Node.js, Express middleware
12
12
  - **Server-to-server** login, exchange, refresh, logout via API v2
13
+ - **Scope array** en JWT para consumir APIs internas
13
14
 
14
15
  ## Instalación
15
16
 
@@ -25,15 +26,15 @@ npm install @bigso/auth-sdk
25
26
  │ (consuming) │ sso-init / sso-success │ (iframe) │
26
27
  └──────┬───────┘ └──────┬───────┘
27
28
  │ │
28
- │ 1. auth.login() → codeVerifier
29
- │ 2. POST /exchange-v2-pkce
30
- │ { code, codeVerifier }
29
+ │ 1. auth.login() → codeVerifier
30
+ │ 2. POST /api/auth/exchange-v2
31
+ │ { payload, codeVerifier }
31
32
  ▼ ▼
32
33
  ┌──────────────┐ POST /api/v2/auth/exchange ┌──────────────┐
33
34
  │ App Backend │──────────────────────────────────►│ SSO Core │
34
35
  │ (Express) │◄─────────────────────────────────│ (API v2) │
35
36
  │ │ { accessToken, refreshToken } │ │
36
- └──────────────┘ └──────────────┘
37
+ └──────────────┘ (con scope array) └──────────────┘
37
38
  ```
38
39
 
39
40
  ## Uso
@@ -44,9 +45,9 @@ npm install @bigso/auth-sdk
44
45
  import { BigsoAuth } from '@bigso/auth-sdk';
45
46
 
46
47
  const auth = new BigsoAuth({
47
- clientId: 'crm',
48
- ssoOrigin: 'https://sso.bigso.co',
49
- jwksUrl: 'https://sso.bigso.co/.well-known/jwks.json',
48
+ clientId: 'ordamy',
49
+ ssoOrigin: 'https://sso-portal.bigso.co',
50
+ jwksUrl: 'https://sso-core.bigso.co/.well-known/jwks.json',
50
51
  });
51
52
 
52
53
  auth.on('success', async (result) => {
@@ -57,11 +58,12 @@ auth.on('success', async (result) => {
57
58
  // result.nonce → matches your original nonce
58
59
 
59
60
  // Send to your backend:
60
- const response = await fetch('/api/auth/exchange-v2-pkce', {
61
+ const response = await fetch('/api/auth/exchange-v2', {
61
62
  method: 'POST',
62
63
  headers: { 'Content-Type': 'application/json' },
63
64
  body: JSON.stringify({
64
65
  payload: result.signed_payload,
66
+ codeVerifier: result.codeVerifier,
65
67
  }),
66
68
  });
67
69
  });
@@ -76,15 +78,15 @@ import { BigsoSsoClient } from '@bigso/auth-sdk/node';
76
78
  import { createSsoAuthRouter, ssoAuthMiddleware } from '@bigso/auth-sdk/express';
77
79
 
78
80
  const ssoClient = new BigsoSsoClient({
79
- ssoBackendUrl: 'https://sso.bigso.co',
80
- ssoJwksUrl: 'https://sso.bigso.co/.well-known/jwks.json',
81
- appId: 'crm',
81
+ ssoBackendUrl: 'https://sso-core.bigso.co',
82
+ ssoJwksUrl: 'https://sso-core.bigso.co/.well-known/jwks.json',
83
+ appId: 'ordamy',
82
84
  });
83
85
 
84
86
  // Auth routes: /exchange, /exchange-v2, /session, /refresh, /logout
85
87
  app.use('/api/auth', createSsoAuthRouter({
86
88
  ssoClient,
87
- frontendUrl: 'https://myapp.com',
89
+ frontendUrl: 'https://ordamy.bigso.co',
88
90
  }));
89
91
 
90
92
  // Protected routes: validates Bearer JWT token
@@ -99,9 +101,9 @@ app.get('/api/protected', ssoAuthMiddleware({ ssoClient }), (req, res) => {
99
101
  import { BigsoSsoClient } from '@bigso/auth-sdk/node';
100
102
 
101
103
  const client = new BigsoSsoClient({
102
- ssoBackendUrl: 'https://sso.bigso.co',
103
- ssoJwksUrl: 'https://sso.bigso.co/.well-known/jwks.json',
104
- appId: 'crm',
104
+ ssoBackendUrl: 'https://sso-core.bigso.co',
105
+ ssoJwksUrl: 'https://sso-core.bigso.co/.well-known/jwks.json',
106
+ appId: 'ordamy',
105
107
  });
106
108
 
107
109
  // Exchange authorization code with PKCE
@@ -126,7 +128,7 @@ await client.logout('eyJhbG...', true); // revokeAll = true
126
128
  | Param | Type | Required | Default | Description |
127
129
  |---|---|---|---|---|
128
130
  | `clientId` | `string` | Yes | — | App ID registered in SSO |
129
- | `ssoOrigin` | `string` | Yes | — | SSO origin (e.g. `https://sso.bigso.co`) |
131
+ | `ssoOrigin` | `string` | Yes | — | SSO origin (e.g. `https://sso-portal.bigso.co`) |
130
132
  | `jwksUrl` | `string` | Yes | — | JWKS URL for JWS verification |
131
133
  | `timeout` | `number` | No | `5000` | Timeout after `sso-ready` (ms) |
132
134
  | `debug` | `boolean` | No | `false` | Debug logging |
@@ -161,7 +163,7 @@ Returns `Promise<BigsoAuthResult>`:
161
163
  | Route | Method | Description |
162
164
  |---|---|---|
163
165
  | `/exchange` | POST | `{code, codeVerifier}` → v2 exchange |
164
- | `/exchange-v2` | POST | `{payload}` → verify JWS, then v2 exchange |
166
+ | `/exchange-v2` | POST | `{payload, codeVerifier?}` → verify JWS, then v2 exchange (codeVerifier from body or JWS) |
165
167
  | `/session` | GET | Validate Bearer token, return user data |
166
168
  | `/refresh` | POST | Proxy to `/api/v2/auth/refresh` |
167
169
  | `/logout` | POST | Bearer token → `/api/v2/auth/logout` |
@@ -177,14 +179,34 @@ Reads `Authorization: Bearer <token>`, validates JWT against JWKS, populates `re
177
179
  2. Browser SDK computa: codeChallenge = SHA256(codeVerifier)
178
180
  3. Browser SDK → iframe: {codeChallenge, state, nonce}
179
181
  4. Iframe → SSO Core: POST /api/v2/auth/authorize (con codeChallenge)
180
- 5. Iframe → Browser SDK: {code, state} (firmado como JWS)
182
+ 5. Iframe → Browser SDK: {code, signedPayload} (JWS contiene code_verifier si se pasó)
181
183
  6. Browser SDK verifica JWS, valida state y nonce
182
- 7. Browser SDK retorna {code, codeVerifier} al consuming app
183
- 8. Consuming app → su backend: POST /exchange-v2-pkce {payload, codeVerifier}
184
+ 7. Browser SDK retorna {code, codeVerifier, signed_payload} al consuming app
185
+ 8. Consuming app → su backend: POST /exchange-v2 {payload, codeVerifier}
184
186
  9. Backend verifica JWS, extrae code, llama /api/v2/auth/exchange {code, appId, codeVerifier}
185
187
  10. SSO Core verifica: SHA256(codeVerifier) === codeChallenge → emite tokens
186
188
  ```
187
189
 
190
+ ## JWT Access Token
191
+
192
+ El access token contiene:
193
+
194
+ ```json
195
+ {
196
+ "sub": "user-uuid",
197
+ "jti": "token-uuid",
198
+ "iss": "https://sso.bigso.co",
199
+ "aud": "https://ordamy.bigso.co",
200
+ "exp": 1234567890,
201
+ "iat": 1234567890,
202
+ "tenants": [{ "id": "...", "name": "...", "slug": "...", "role": "...", "apps": ["ordamy"] }],
203
+ "systemRole": "user",
204
+ "scope": ["https://ordamy.bigso.co", "https://api-interna.bigso.co"]
205
+ }
206
+ ```
207
+
208
+ El campo `scope` define qué APIs puede consumir este token. Cada aplicación tiene su `scope` configurado en la BD de SSO Core.
209
+
188
210
  ## Seguridad
189
211
 
190
212
  - **PKCE**: codeVerifier nunca sale del browser hasta el paso 7, pero jamás se envía al SSO iframe
@@ -192,6 +214,7 @@ Reads `Authorization: Bearer <token>`, validates JWT against JWKS, populates `re
192
214
  - **state + nonce**: Validados en ambos lados para prevenir CSRF y replay
193
215
  - **JWT Bearer**: Access tokens validados localmente contra JWKS, revocables en Redis/PG
194
216
  - **httpOnly cookies**: Refresh tokens via cookie, nunca accesibles via JS
217
+ - **Scope validation**: APIs internas deben verificar que su URL esté en el array `scope` del token
195
218
 
196
219
  ## Desarrollo
197
220
 
@@ -204,6 +227,22 @@ npm test # vitest
204
227
 
205
228
  ## Changelog
206
229
 
230
+ ### v0.5.3 (2026-04-09)
231
+
232
+ - `exchange-v2` ahora acepta `codeVerifier` del body del request o del JWS (antes solo del JWS)
233
+ - Fix de fallback: `buildFallbackUrl()` ahora incluye `code_challenge` en la URL
234
+
235
+ ### v0.5.2 (2026-04-08)
236
+
237
+ - Nuevo campo `scope?: string[]` en `SsoTokenPayload`
238
+ - `verifyAccessToken()` mapea `scope` del JWT payload
239
+ - `prepublishOnly` script agregado para build automático antes de publish
240
+
241
+ ### v0.5.1 (2026-04-08)
242
+
243
+ - Fix: SDK sin dist/ — `prepublishOnly: "npm run build"` agregado
244
+ - `SsoJwtTenant` con `apps: string[]`
245
+
207
246
  ### v0.5.0 (2026-04-07) — Full v2
208
247
 
209
248
  **Breaking changes:**
@@ -219,7 +258,7 @@ npm test # vitest
219
258
  - `BigsoSsoClient.refreshTokens()` — via `/api/v2/auth/refresh`
220
259
  - `BigsoSsoClient.logout(accessToken)` — via `/api/v2/auth/logout`
221
260
  - `BigsoSsoClient.validateAccessToken(token)` — Local JWT verification against JWKS
222
- - Express `/exchange-v2-pkce` route with full PKCE support
261
+ - Express `/exchange-v2` route with full PKCE support
223
262
  - Express `/refresh` and `/logout` routes for v2 API
224
263
  - `ssoAuthMiddleware` validates Bearer JWT tokens locally
225
264
 
@@ -122,7 +122,7 @@ function createSsoAuthRouter(options) {
122
122
  });
123
123
  router.post("/exchange-v2", async (req, res) => {
124
124
  try {
125
- const { payload } = req.body;
125
+ const { payload, codeVerifier: codeVerifierFromBody } = req.body;
126
126
  if (!payload) {
127
127
  res.status(400).json({ error: "Signed payload is required" });
128
128
  return;
@@ -132,12 +132,12 @@ function createSsoAuthRouter(options) {
132
132
  res.status(400).json({ error: "No authorization code found in payload" });
133
133
  return;
134
134
  }
135
- const codeVerifier = verified.code_verifier;
136
- if (!codeVerifier) {
137
- res.status(400).json({ error: "code_verifier is required for PKCE exchange" });
135
+ const verifier = codeVerifierFromBody || verified.code_verifier;
136
+ if (!verifier) {
137
+ res.status(400).json({ error: "codeVerifier is required for PKCE exchange" });
138
138
  return;
139
139
  }
140
- const ssoResponse = await options.ssoClient.exchangeCode(verified.code, codeVerifier);
140
+ const ssoResponse = await options.ssoClient.exchangeCode(verified.code, verifier);
141
141
  if (options.onLoginSuccess) {
142
142
  await options.onLoginSuccess(ssoResponse);
143
143
  }
@@ -93,7 +93,7 @@ function createSsoAuthRouter(options) {
93
93
  });
94
94
  router.post("/exchange-v2", async (req, res) => {
95
95
  try {
96
- const { payload } = req.body;
96
+ const { payload, codeVerifier: codeVerifierFromBody } = req.body;
97
97
  if (!payload) {
98
98
  res.status(400).json({ error: "Signed payload is required" });
99
99
  return;
@@ -103,12 +103,12 @@ function createSsoAuthRouter(options) {
103
103
  res.status(400).json({ error: "No authorization code found in payload" });
104
104
  return;
105
105
  }
106
- const codeVerifier = verified.code_verifier;
107
- if (!codeVerifier) {
108
- res.status(400).json({ error: "code_verifier is required for PKCE exchange" });
106
+ const verifier = codeVerifierFromBody || verified.code_verifier;
107
+ if (!verifier) {
108
+ res.status(400).json({ error: "codeVerifier is required for PKCE exchange" });
109
109
  return;
110
110
  }
111
- const ssoResponse = await options.ssoClient.exchangeCode(verified.code, codeVerifier);
111
+ const ssoResponse = await options.ssoClient.exchangeCode(verified.code, verifier);
112
112
  if (options.onLoginSuccess) {
113
113
  await options.onLoginSuccess(ssoResponse);
114
114
  }
@@ -108,10 +108,13 @@ var BigsoSsoClient = class {
108
108
  }
109
109
  return await response.json();
110
110
  }
111
- async refreshTokens() {
111
+ async refreshTokens(refreshToken) {
112
+ const headers = { "Content-Type": "application/json" };
113
+ const body = refreshToken ? JSON.stringify({ refreshToken }) : void 0;
112
114
  const response = await fetch(`${this.ssoBackendUrl}/api/v2/auth/refresh`, {
113
115
  method: "POST",
114
- headers: { "Content-Type": "application/json" },
116
+ headers,
117
+ body,
115
118
  credentials: "include"
116
119
  });
117
120
  if (!response.ok) {
@@ -14,7 +14,7 @@ declare class BigsoSsoClient {
14
14
  validateAccessToken(accessToken: string): Promise<SsoTokenPayload | null>;
15
15
  login(emailOrNuid: string, password: string): Promise<V2LoginResponse>;
16
16
  exchangeCode(code: string, codeVerifier: string): Promise<V2ExchangeResponse>;
17
- refreshTokens(): Promise<V2RefreshResponse>;
17
+ refreshTokens(refreshToken?: string): Promise<V2RefreshResponse>;
18
18
  logout(accessToken: string, revokeAll?: boolean): Promise<void>;
19
19
  }
20
20
 
@@ -14,7 +14,7 @@ declare class BigsoSsoClient {
14
14
  validateAccessToken(accessToken: string): Promise<SsoTokenPayload | null>;
15
15
  login(emailOrNuid: string, password: string): Promise<V2LoginResponse>;
16
16
  exchangeCode(code: string, codeVerifier: string): Promise<V2ExchangeResponse>;
17
- refreshTokens(): Promise<V2RefreshResponse>;
17
+ refreshTokens(refreshToken?: string): Promise<V2RefreshResponse>;
18
18
  logout(accessToken: string, revokeAll?: boolean): Promise<void>;
19
19
  }
20
20
 
@@ -58,10 +58,13 @@ var BigsoSsoClient = class {
58
58
  }
59
59
  return await response.json();
60
60
  }
61
- async refreshTokens() {
61
+ async refreshTokens(refreshToken) {
62
+ const headers = { "Content-Type": "application/json" };
63
+ const body = refreshToken ? JSON.stringify({ refreshToken }) : void 0;
62
64
  const response = await fetch(`${this.ssoBackendUrl}/api/v2/auth/refresh`, {
63
65
  method: "POST",
64
- headers: { "Content-Type": "application/json" },
66
+ headers,
67
+ body,
65
68
  credentials: "include"
66
69
  });
67
70
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigso/auth-sdk",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "SDK de autenticación para SSO v2 - JWT Bearer + PKCE",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",