@bigso/auth-sdk 0.5.3 → 0.5.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 +60 -21
- package/dist/express/index.cjs +5 -5
- package/dist/express/index.js +5 -5
- package/package.json +1 -1
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
|
|
30
|
-
│ {
|
|
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: '
|
|
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
|
|
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: '
|
|
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://
|
|
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: '
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
package/dist/express/index.cjs
CHANGED
|
@@ -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
|
|
136
|
-
if (!
|
|
137
|
-
res.status(400).json({ error: "
|
|
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,
|
|
140
|
+
const ssoResponse = await options.ssoClient.exchangeCode(verified.code, verifier);
|
|
141
141
|
if (options.onLoginSuccess) {
|
|
142
142
|
await options.onLoginSuccess(ssoResponse);
|
|
143
143
|
}
|
package/dist/express/index.js
CHANGED
|
@@ -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
|
|
107
|
-
if (!
|
|
108
|
-
res.status(400).json({ error: "
|
|
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,
|
|
111
|
+
const ssoResponse = await options.ssoClient.exchangeCode(verified.code, verifier);
|
|
112
112
|
if (options.onLoginSuccess) {
|
|
113
113
|
await options.onLoginSuccess(ssoResponse);
|
|
114
114
|
}
|