@tdacorp/identity-client 0.2.1 → 0.2.3

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.
@@ -129,6 +129,16 @@ var TokenRequestError = class extends Error {
129
129
  this.errorDescription = details.errorDescription;
130
130
  }
131
131
  };
132
+ async function fetchDiscoveryForTokenRequest(issuer) {
133
+ try {
134
+ return await fetchDiscovery(issuer);
135
+ } catch (error) {
136
+ throw new TokenRequestError({
137
+ error: "discovery_failed",
138
+ errorDescription: error instanceof Error ? error.message : "Failed to fetch the issuer's discovery document"
139
+ });
140
+ }
141
+ }
132
142
  function toTokenSet(body) {
133
143
  return {
134
144
  accessToken: body.access_token,
@@ -159,7 +169,7 @@ async function readTokenResponseBody(response) {
159
169
  return await response.json().catch(() => null);
160
170
  }
161
171
  async function exchangeAuthorizationCode(options) {
162
- const discovery = await fetchDiscovery(options.issuer);
172
+ const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
163
173
  const response = await postTokenRequest(
164
174
  discovery.token_endpoint,
165
175
  options.clientId,
@@ -179,7 +189,7 @@ async function exchangeAuthorizationCode(options) {
179
189
  return toTokenSet(body);
180
190
  }
181
191
  async function clientCredentialsGrant(options) {
182
- const discovery = await fetchDiscovery(options.issuer);
192
+ const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
183
193
  const response = await postTokenRequest(
184
194
  discovery.token_endpoint,
185
195
  options.clientId,
@@ -306,6 +316,12 @@ async function verifyIdToken(idToken, options) {
306
316
  const result = await verifyAgainstIssuer(idToken, options);
307
317
  if ("errors" in result) return { success: false, errors: result.errors };
308
318
  const { payload } = result;
319
+ if ("token_use" in payload) {
320
+ return {
321
+ success: false,
322
+ errors: [{ code: "wrong-token-type", message: "Token has a token_use claim; an id_token never carries one. This is an access token, not an id_token." }]
323
+ };
324
+ }
309
325
  if (typeof payload.sub !== "string") {
310
326
  return { success: false, errors: [{ code: "malformed-claims", message: "id_token has no sub claim" }] };
311
327
  }
@@ -315,6 +331,12 @@ async function verifyAccessToken(accessToken, options) {
315
331
  const result = await verifyAgainstIssuer(accessToken, options);
316
332
  if ("errors" in result) return { success: false, errors: result.errors };
317
333
  const { payload } = result;
334
+ if (payload.token_use !== "oauth_access") {
335
+ return {
336
+ success: false,
337
+ errors: [{ code: "wrong-token-type", message: 'Token is missing token_use: "oauth_access". This is not an access token (likely an id_token).' }]
338
+ };
339
+ }
318
340
  const claims = payload;
319
341
  const roles = "roles" in claims ? claims.roles : void 0;
320
342
  if (typeof payload.sub === "string") {
package/dist/index.cjs CHANGED
@@ -166,6 +166,16 @@ var TokenRequestError = class extends Error {
166
166
  this.errorDescription = details.errorDescription;
167
167
  }
168
168
  };
169
+ async function fetchDiscoveryForTokenRequest(issuer) {
170
+ try {
171
+ return await fetchDiscovery(issuer);
172
+ } catch (error) {
173
+ throw new TokenRequestError({
174
+ error: "discovery_failed",
175
+ errorDescription: error instanceof Error ? error.message : "Failed to fetch the issuer's discovery document"
176
+ });
177
+ }
178
+ }
169
179
  function toTokenSet(body) {
170
180
  return {
171
181
  accessToken: body.access_token,
@@ -196,7 +206,7 @@ async function readTokenResponseBody(response) {
196
206
  return await response.json().catch(() => null);
197
207
  }
198
208
  async function exchangeAuthorizationCode(options) {
199
- const discovery = await fetchDiscovery(options.issuer);
209
+ const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
200
210
  const response = await postTokenRequest(
201
211
  discovery.token_endpoint,
202
212
  options.clientId,
@@ -216,7 +226,7 @@ async function exchangeAuthorizationCode(options) {
216
226
  return toTokenSet(body);
217
227
  }
218
228
  async function clientCredentialsGrant(options) {
219
- const discovery = await fetchDiscovery(options.issuer);
229
+ const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
220
230
  const response = await postTokenRequest(
221
231
  discovery.token_endpoint,
222
232
  options.clientId,
@@ -343,6 +353,12 @@ async function verifyIdToken(idToken, options) {
343
353
  const result = await verifyAgainstIssuer(idToken, options);
344
354
  if ("errors" in result) return { success: false, errors: result.errors };
345
355
  const { payload } = result;
356
+ if ("token_use" in payload) {
357
+ return {
358
+ success: false,
359
+ errors: [{ code: "wrong-token-type", message: "Token has a token_use claim; an id_token never carries one. This is an access token, not an id_token." }]
360
+ };
361
+ }
346
362
  if (typeof payload.sub !== "string") {
347
363
  return { success: false, errors: [{ code: "malformed-claims", message: "id_token has no sub claim" }] };
348
364
  }
@@ -352,6 +368,12 @@ async function verifyAccessToken(accessToken, options) {
352
368
  const result = await verifyAgainstIssuer(accessToken, options);
353
369
  if ("errors" in result) return { success: false, errors: result.errors };
354
370
  const { payload } = result;
371
+ if (payload.token_use !== "oauth_access") {
372
+ return {
373
+ success: false,
374
+ errors: [{ code: "wrong-token-type", message: 'Token is missing token_use: "oauth_access". This is not an access token (likely an id_token).' }]
375
+ };
376
+ }
355
377
  const claims = payload;
356
378
  const roles = "roles" in claims ? claims.roles : void 0;
357
379
  if (typeof payload.sub === "string") {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRemoteJWKSet } from 'jose';
2
- export { A as AuthorizationCodeExchangeOptions, C as CLOCK_SKEW_TOLERANCE_SECONDS, a as ClientCredentialsGrantOptions, I as IdTokenClaims, R as RefreshTokensOptions, T as TokenEndpointAuthMethod, b as TokenRefreshOutcome, c as TokenRequestError, d as TokenSet, e as TokenVerificationError, f as TokenVerificationErrorCode, V as VerificationResult, g as VerifiedAccessToken, h as VerifyTokenOptions, i as clientCredentialsGrant, j as exchangeAuthorizationCode, r as refreshTokens, v as verifyAccessToken, k as verifyIdToken } from './verify-C1Ga6JbS.cjs';
2
+ export { A as AuthorizationCodeExchangeOptions, C as CLOCK_SKEW_TOLERANCE_SECONDS, a as ClientCredentialsGrantOptions, I as IdTokenClaims, R as RefreshTokensOptions, T as TokenEndpointAuthMethod, b as TokenRefreshOutcome, c as TokenRequestError, d as TokenSet, e as TokenVerificationError, f as TokenVerificationErrorCode, V as VerificationResult, g as VerifiedAccessToken, h as VerifyTokenOptions, i as clientCredentialsGrant, j as exchangeAuthorizationCode, r as refreshTokens, v as verifyAccessToken, k as verifyIdToken } from './verify-ggSzUrdC.cjs';
3
3
  import '@tdacorp/identity-authz';
4
4
 
5
5
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRemoteJWKSet } from 'jose';
2
- export { A as AuthorizationCodeExchangeOptions, C as CLOCK_SKEW_TOLERANCE_SECONDS, a as ClientCredentialsGrantOptions, I as IdTokenClaims, R as RefreshTokensOptions, T as TokenEndpointAuthMethod, b as TokenRefreshOutcome, c as TokenRequestError, d as TokenSet, e as TokenVerificationError, f as TokenVerificationErrorCode, V as VerificationResult, g as VerifiedAccessToken, h as VerifyTokenOptions, i as clientCredentialsGrant, j as exchangeAuthorizationCode, r as refreshTokens, v as verifyAccessToken, k as verifyIdToken } from './verify-C1Ga6JbS.js';
2
+ export { A as AuthorizationCodeExchangeOptions, C as CLOCK_SKEW_TOLERANCE_SECONDS, a as ClientCredentialsGrantOptions, I as IdTokenClaims, R as RefreshTokensOptions, T as TokenEndpointAuthMethod, b as TokenRefreshOutcome, c as TokenRequestError, d as TokenSet, e as TokenVerificationError, f as TokenVerificationErrorCode, V as VerificationResult, g as VerifiedAccessToken, h as VerifyTokenOptions, i as clientCredentialsGrant, j as exchangeAuthorizationCode, r as refreshTokens, v as verifyAccessToken, k as verifyIdToken } from './verify-ggSzUrdC.js';
3
3
  import '@tdacorp/identity-authz';
4
4
 
5
5
  /**
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  refreshTokens,
12
12
  verifyAccessToken,
13
13
  verifyIdToken
14
- } from "./chunk-S5WGUOJA.js";
14
+ } from "./chunk-MB66JUMG.js";
15
15
  export {
16
16
  CLOCK_SKEW_TOLERANCE_SECONDS,
17
17
  TokenRequestError,
package/dist/next.cjs CHANGED
@@ -157,6 +157,16 @@ var TokenRequestError = class extends Error {
157
157
  this.errorDescription = details.errorDescription;
158
158
  }
159
159
  };
160
+ async function fetchDiscoveryForTokenRequest(issuer) {
161
+ try {
162
+ return await fetchDiscovery(issuer);
163
+ } catch (error) {
164
+ throw new TokenRequestError({
165
+ error: "discovery_failed",
166
+ errorDescription: error instanceof Error ? error.message : "Failed to fetch the issuer's discovery document"
167
+ });
168
+ }
169
+ }
160
170
  function toTokenSet(body) {
161
171
  return {
162
172
  accessToken: body.access_token,
@@ -187,7 +197,7 @@ async function readTokenResponseBody(response) {
187
197
  return await response.json().catch(() => null);
188
198
  }
189
199
  async function exchangeAuthorizationCode(options) {
190
- const discovery = await fetchDiscovery(options.issuer);
200
+ const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
191
201
  const response = await postTokenRequest(
192
202
  discovery.token_endpoint,
193
203
  options.clientId,
@@ -280,6 +290,12 @@ async function verifyIdToken(idToken, options) {
280
290
  const result = await verifyAgainstIssuer(idToken, options);
281
291
  if ("errors" in result) return { success: false, errors: result.errors };
282
292
  const { payload } = result;
293
+ if ("token_use" in payload) {
294
+ return {
295
+ success: false,
296
+ errors: [{ code: "wrong-token-type", message: "Token has a token_use claim; an id_token never carries one. This is an access token, not an id_token." }]
297
+ };
298
+ }
283
299
  if (typeof payload.sub !== "string") {
284
300
  return { success: false, errors: [{ code: "malformed-claims", message: "id_token has no sub claim" }] };
285
301
  }
package/dist/next.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextResponse, NextRequest } from 'next/server';
2
- import { d as TokenSet, I as IdTokenClaims } from './verify-C1Ga6JbS.cjs';
2
+ import { d as TokenSet, I as IdTokenClaims } from './verify-ggSzUrdC.cjs';
3
3
  import { SealSecret } from './sealed.cjs';
4
4
  import '@tdacorp/identity-authz';
5
5
  import 'jose';
package/dist/next.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextResponse, NextRequest } from 'next/server';
2
- import { d as TokenSet, I as IdTokenClaims } from './verify-C1Ga6JbS.js';
2
+ import { d as TokenSet, I as IdTokenClaims } from './verify-ggSzUrdC.js';
3
3
  import { SealSecret } from './sealed.js';
4
4
  import '@tdacorp/identity-authz';
5
5
  import 'jose';
package/dist/next.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  generateCodeVerifier,
6
6
  generateState,
7
7
  verifyIdToken
8
- } from "./chunk-S5WGUOJA.js";
8
+ } from "./chunk-MB66JUMG.js";
9
9
  import {
10
10
  seal,
11
11
  unseal
@@ -246,8 +246,14 @@ declare const CLOCK_SKEW_TOLERANCE_SECONDS = 60;
246
246
  * - `'malformed-token'`: the token is not a well-formed JWT at all.
247
247
  * - `'jwks-unavailable'`: the issuer's discovery document or JWKS could not
248
248
  * be fetched -- infrastructure, not a verdict on the token.
249
+ * - `'wrong-token-type'`: the token verified (right issuer, right audience,
250
+ * right signature) but is the OTHER kind of token this server mints for a
251
+ * login -- an id_token presented to `verifyAccessToken`, or an access
252
+ * token presented to `verifyIdToken`. The two share `iss`/`sub`/`aud`/`azp`
253
+ * and are signed by the same key, so nothing else here would catch this;
254
+ * see `token_use` on `IdentityTokenPayload`.
249
255
  */
250
- type TokenVerificationErrorCode = 'expired' | 'invalid-signature' | 'invalid-issuer' | 'invalid-audience' | 'unauthorized-party' | 'malformed-claims' | 'malformed-token' | 'jwks-unavailable';
256
+ type TokenVerificationErrorCode = 'expired' | 'invalid-signature' | 'invalid-issuer' | 'invalid-audience' | 'unauthorized-party' | 'malformed-claims' | 'malformed-token' | 'jwks-unavailable' | 'wrong-token-type';
251
257
  /** One verification failure: a stable `code` to branch on, plus a
252
258
  * human-readable `message` for logs. `verifyIdToken`/`verifyAccessToken`
253
259
  * return a list of these rather than throwing -- see `VerificationResult`. */
@@ -246,8 +246,14 @@ declare const CLOCK_SKEW_TOLERANCE_SECONDS = 60;
246
246
  * - `'malformed-token'`: the token is not a well-formed JWT at all.
247
247
  * - `'jwks-unavailable'`: the issuer's discovery document or JWKS could not
248
248
  * be fetched -- infrastructure, not a verdict on the token.
249
+ * - `'wrong-token-type'`: the token verified (right issuer, right audience,
250
+ * right signature) but is the OTHER kind of token this server mints for a
251
+ * login -- an id_token presented to `verifyAccessToken`, or an access
252
+ * token presented to `verifyIdToken`. The two share `iss`/`sub`/`aud`/`azp`
253
+ * and are signed by the same key, so nothing else here would catch this;
254
+ * see `token_use` on `IdentityTokenPayload`.
249
255
  */
250
- type TokenVerificationErrorCode = 'expired' | 'invalid-signature' | 'invalid-issuer' | 'invalid-audience' | 'unauthorized-party' | 'malformed-claims' | 'malformed-token' | 'jwks-unavailable';
256
+ type TokenVerificationErrorCode = 'expired' | 'invalid-signature' | 'invalid-issuer' | 'invalid-audience' | 'unauthorized-party' | 'malformed-claims' | 'malformed-token' | 'jwks-unavailable' | 'wrong-token-type';
251
257
  /** One verification failure: a stable `code` to branch on, plus a
252
258
  * human-readable `message` for logs. `verifyIdToken`/`verifyAccessToken`
253
259
  * return a list of these rather than throwing -- see `VerificationResult`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdacorp/identity-client",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "OIDC relying-party client for TDACorp Identity: discovery, PKCE, token exchange, token verification and Next.js route helpers.",
5
5
  "license": "MIT",
6
6
  "bugs": {
@@ -55,7 +55,7 @@
55
55
  ],
56
56
  "dependencies": {
57
57
  "jose": "^6.2.9",
58
- "@tdacorp/identity-authz": "^0.2.0"
58
+ "@tdacorp/identity-authz": "^0.2.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "next": ">=14"