@connectid-tools/rp-nodejs-sdk 4.2.1 → 5.0.1
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 +64 -71
- package/config.js +2 -31
- package/conformance/api/conformance-api.d.ts +38 -0
- package/conformance/api/conformance-api.js +53 -0
- package/conformance/conformance-config.d.ts +2 -0
- package/conformance/conformance-config.js +34 -0
- package/crypto/crypto-loader.d.ts +32 -0
- package/crypto/crypto-loader.js +49 -0
- package/crypto/jwt-helper.d.ts +61 -0
- package/crypto/jwt-helper.js +92 -0
- package/crypto/pkce-helper.d.ts +43 -0
- package/crypto/pkce-helper.js +75 -0
- package/endpoints/participants-endpoint.d.ts +55 -0
- package/endpoints/participants-endpoint.js +137 -0
- package/endpoints/pushed-authorisation-request-endpoint.d.ts +87 -0
- package/endpoints/pushed-authorisation-request-endpoint.js +192 -0
- package/endpoints/retrieve-token-endpoint.d.ts +66 -0
- package/endpoints/retrieve-token-endpoint.js +159 -0
- package/endpoints/userinfo-endpoint.d.ts +24 -0
- package/endpoints/userinfo-endpoint.js +50 -0
- package/fapi/fapi-utils.d.ts +6 -0
- package/fapi/fapi-utils.js +9 -0
- package/http/http-client-extensions.d.ts +60 -0
- package/http/http-client-extensions.js +106 -0
- package/http/http-client-factory.d.ts +27 -0
- package/http/http-client-factory.js +45 -0
- package/model/callback-params.d.ts +31 -0
- package/model/callback-params.js +1 -0
- package/model/claims.d.ts +100 -0
- package/model/claims.js +1 -0
- package/model/consolidated-token-set.d.ts +74 -0
- package/model/consolidated-token-set.js +100 -0
- package/model/discovery-service.d.ts +46 -0
- package/model/discovery-service.js +112 -0
- package/model/issuer-metadata.d.ts +165 -0
- package/model/issuer-metadata.js +1 -0
- package/model/jwks.d.ts +12 -0
- package/model/jwks.js +1 -0
- package/model/token-response.d.ts +31 -0
- package/model/token-response.js +1 -0
- package/model/token-set.d.ts +73 -0
- package/model/token-set.js +179 -0
- package/package.json +4 -5
- package/relying-party-client-sdk.d.ts +55 -24
- package/relying-party-client-sdk.js +90 -304
- package/test-data/large-participants-test-data.d.ts +865 -0
- package/test-data/large-participants-test-data.js +18907 -0
- package/test-data/participants-test-data.d.ts +149 -0
- package/test-data/participants-test-data.js +458 -0
- package/test-data/sandbox-participants-test-data.d.ts +865 -0
- package/test-data/sandbox-participants-test-data.js +3794 -0
- package/types.d.ts +61 -32
- package/utils/request-utils.d.ts +1 -1
- package/utils/request-utils.js +5 -5
- package/utils/user-agent.d.ts +1 -1
- package/utils/user-agent.js +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { IdTokenClaims } from './claims.js';
|
|
2
|
+
import { TokenResponse } from './token-response.js';
|
|
3
|
+
import { JWKSet } from './jwks.js';
|
|
4
|
+
/**
|
|
5
|
+
* Token Set
|
|
6
|
+
*
|
|
7
|
+
* Represents an OAuth 2.0 / OIDC token response with validation capabilities.
|
|
8
|
+
* Handles ID token validation and claims extraction.
|
|
9
|
+
*/
|
|
10
|
+
export declare class TokenSet {
|
|
11
|
+
readonly access_token?: string;
|
|
12
|
+
readonly token_type?: string;
|
|
13
|
+
readonly expires_in?: number;
|
|
14
|
+
readonly refresh_token?: string;
|
|
15
|
+
readonly scope?: string;
|
|
16
|
+
readonly id_token?: string;
|
|
17
|
+
private idTokenClaims?;
|
|
18
|
+
private jwtPayload?;
|
|
19
|
+
private tokenIssuedAt;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new TokenSet from a token response.
|
|
22
|
+
*
|
|
23
|
+
* @param tokenResponse - Raw token response from the token endpoint
|
|
24
|
+
*/
|
|
25
|
+
constructor(tokenResponse: TokenResponse);
|
|
26
|
+
/**
|
|
27
|
+
* Validates the ID token.
|
|
28
|
+
*
|
|
29
|
+
* Performs the following validations:
|
|
30
|
+
* - Algorithm validation against allowed algorithms
|
|
31
|
+
* - Signature verification using JWKS
|
|
32
|
+
* - Issuer validation
|
|
33
|
+
* - Audience validation
|
|
34
|
+
* - Nonce validation
|
|
35
|
+
* - Timestamp validation (iat, exp)
|
|
36
|
+
*
|
|
37
|
+
* @param jwks - JSON Web Key Set for signature verification
|
|
38
|
+
* @param expectedIssuer - Expected issuer claim value
|
|
39
|
+
* @param expectedAudience - Expected audience claim value
|
|
40
|
+
* @param expectedNonce - Expected nonce value
|
|
41
|
+
* @param allowedAlgorithms - Optional list of allowed signing algorithms from discovery document
|
|
42
|
+
* @throws Error if validation fails
|
|
43
|
+
*/
|
|
44
|
+
validate(jwks: JWKSet, expectedIssuer: string, expectedAudience: string, expectedNonce: string, allowedAlgorithms?: string[]): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the parsed ID token claims.
|
|
47
|
+
*
|
|
48
|
+
* Must call validate() first.
|
|
49
|
+
*
|
|
50
|
+
* @returns Parsed and validated ID token claims
|
|
51
|
+
* @throws Error if token has not been validated
|
|
52
|
+
*/
|
|
53
|
+
claims(): IdTokenClaims;
|
|
54
|
+
/**
|
|
55
|
+
* Checks if the access token has expired.
|
|
56
|
+
*
|
|
57
|
+
* @returns true if the token is expired, false otherwise
|
|
58
|
+
*/
|
|
59
|
+
expired(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Selects the appropriate key from JWKS for verification.
|
|
62
|
+
*
|
|
63
|
+
* Matches based on:
|
|
64
|
+
* - kid (key ID)
|
|
65
|
+
* - alg (algorithm)
|
|
66
|
+
* - use (key usage - should be 'sig')
|
|
67
|
+
*
|
|
68
|
+
* @param jwks - JSON Web Key Set
|
|
69
|
+
* @returns Imported crypto key for verification
|
|
70
|
+
* @throws Error if no matching key is found
|
|
71
|
+
*/
|
|
72
|
+
private selectKey;
|
|
73
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { jwtVerify, decodeProtectedHeader, importJWK } from 'jose';
|
|
2
|
+
/**
|
|
3
|
+
* Token Set
|
|
4
|
+
*
|
|
5
|
+
* Represents an OAuth 2.0 / OIDC token response with validation capabilities.
|
|
6
|
+
* Handles ID token validation and claims extraction.
|
|
7
|
+
*/
|
|
8
|
+
export class TokenSet {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new TokenSet from a token response.
|
|
11
|
+
*
|
|
12
|
+
* @param tokenResponse - Raw token response from the token endpoint
|
|
13
|
+
*/
|
|
14
|
+
constructor(tokenResponse) {
|
|
15
|
+
this.access_token = tokenResponse.access_token;
|
|
16
|
+
this.token_type = tokenResponse.token_type;
|
|
17
|
+
this.expires_in = tokenResponse.expires_in;
|
|
18
|
+
this.refresh_token = tokenResponse.refresh_token;
|
|
19
|
+
this.scope = tokenResponse.scope;
|
|
20
|
+
this.id_token = tokenResponse.id_token;
|
|
21
|
+
this.tokenIssuedAt = Math.floor(Date.now() / 1000);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validates the ID token.
|
|
25
|
+
*
|
|
26
|
+
* Performs the following validations:
|
|
27
|
+
* - Algorithm validation against allowed algorithms
|
|
28
|
+
* - Signature verification using JWKS
|
|
29
|
+
* - Issuer validation
|
|
30
|
+
* - Audience validation
|
|
31
|
+
* - Nonce validation
|
|
32
|
+
* - Timestamp validation (iat, exp)
|
|
33
|
+
*
|
|
34
|
+
* @param jwks - JSON Web Key Set for signature verification
|
|
35
|
+
* @param expectedIssuer - Expected issuer claim value
|
|
36
|
+
* @param expectedAudience - Expected audience claim value
|
|
37
|
+
* @param expectedNonce - Expected nonce value
|
|
38
|
+
* @param allowedAlgorithms - Optional list of allowed signing algorithms from discovery document
|
|
39
|
+
* @throws Error if validation fails
|
|
40
|
+
*/
|
|
41
|
+
async validate(jwks, expectedIssuer, expectedAudience, expectedNonce, allowedAlgorithms) {
|
|
42
|
+
if (!this.id_token) {
|
|
43
|
+
throw new Error('No id_token to validate');
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
// Decode header to check algorithm before verification
|
|
47
|
+
const header = decodeProtectedHeader(this.id_token);
|
|
48
|
+
// Validate algorithm if allowed algorithms are provided
|
|
49
|
+
if (allowedAlgorithms && allowedAlgorithms.length > 0) {
|
|
50
|
+
if (!header.alg) {
|
|
51
|
+
throw new Error('ID token missing alg in header');
|
|
52
|
+
}
|
|
53
|
+
const isAllowed = allowedAlgorithms.some((alg) => alg.toLowerCase() === header.alg.toLowerCase());
|
|
54
|
+
if (!isAllowed) {
|
|
55
|
+
throw new Error(`ID token algorithm '${header.alg}' is not one of the allowed algorithms: ${allowedAlgorithms.join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Select the appropriate key from JWKS
|
|
59
|
+
const key = await this.selectKey(jwks);
|
|
60
|
+
// Verify signature and standard claims
|
|
61
|
+
const { payload } = await jwtVerify(this.id_token, key, {
|
|
62
|
+
issuer: expectedIssuer,
|
|
63
|
+
audience: expectedAudience,
|
|
64
|
+
algorithms: ['PS256', 'RS256'], // Accept both algorithms
|
|
65
|
+
});
|
|
66
|
+
// Validate nonce
|
|
67
|
+
if (payload.nonce !== expectedNonce) {
|
|
68
|
+
throw new Error(`Nonce mismatch: expected ${expectedNonce}, got ${payload.nonce}`);
|
|
69
|
+
}
|
|
70
|
+
// Validate audience claim per OpenID Connect Core 3.1.3.7
|
|
71
|
+
// If aud is an array, all audiences must be trusted (only our client ID is trusted)
|
|
72
|
+
// and azp claim should be present
|
|
73
|
+
if (Array.isArray(payload.aud)) {
|
|
74
|
+
// Check if all audiences are trusted (only client ID is trusted)
|
|
75
|
+
const untrustedAudiences = payload.aud.filter((aud) => aud !== expectedAudience);
|
|
76
|
+
if (untrustedAudiences.length > 0) {
|
|
77
|
+
throw new Error(`ID token contains untrusted audiences: ${untrustedAudiences.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
// Per OIDC spec clause 4: if multiple audiences, azp should be present
|
|
80
|
+
if (payload.aud.length > 1 && !payload.azp) {
|
|
81
|
+
throw new Error('ID token contains multiple audiences but azp claim is missing');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Validate required claims are present
|
|
85
|
+
const now = Math.floor(Date.now() / 1000);
|
|
86
|
+
if (!payload.iat) {
|
|
87
|
+
throw new Error('ID token missing iat claim');
|
|
88
|
+
}
|
|
89
|
+
if (!payload.exp) {
|
|
90
|
+
throw new Error('ID token missing exp claim');
|
|
91
|
+
}
|
|
92
|
+
// Validate iat (must not be more than 10 minutes old per Java SDK)
|
|
93
|
+
if (now - payload.iat > 600) {
|
|
94
|
+
throw new Error(`ID token iat is too old: issued at ${payload.iat}, current time ${now}`);
|
|
95
|
+
}
|
|
96
|
+
// Validate exp (must not be more than 5 minutes in the past - clock skew tolerance)
|
|
97
|
+
if (payload.exp < now - 300) {
|
|
98
|
+
throw new Error(`ID token expired more than 5 minutes ago: exp ${payload.exp}, current time ${now}`);
|
|
99
|
+
}
|
|
100
|
+
// Store validated claims
|
|
101
|
+
this.jwtPayload = payload;
|
|
102
|
+
this.idTokenClaims = payload;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw new Error(`ID token validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns the parsed ID token claims.
|
|
110
|
+
*
|
|
111
|
+
* Must call validate() first.
|
|
112
|
+
*
|
|
113
|
+
* @returns Parsed and validated ID token claims
|
|
114
|
+
* @throws Error if token has not been validated
|
|
115
|
+
*/
|
|
116
|
+
claims() {
|
|
117
|
+
if (!this.idTokenClaims) {
|
|
118
|
+
throw new Error('ID token has not been validated. Call validate() first.');
|
|
119
|
+
}
|
|
120
|
+
return this.idTokenClaims;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Checks if the access token has expired.
|
|
124
|
+
*
|
|
125
|
+
* @returns true if the token is expired, false otherwise
|
|
126
|
+
*/
|
|
127
|
+
expired() {
|
|
128
|
+
if (!this.expires_in) {
|
|
129
|
+
// If no expires_in, assume token doesn't expire
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const now = Math.floor(Date.now() / 1000);
|
|
133
|
+
const expiresAt = this.tokenIssuedAt + this.expires_in;
|
|
134
|
+
return now >= expiresAt;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Selects the appropriate key from JWKS for verification.
|
|
138
|
+
*
|
|
139
|
+
* Matches based on:
|
|
140
|
+
* - kid (key ID)
|
|
141
|
+
* - alg (algorithm)
|
|
142
|
+
* - use (key usage - should be 'sig')
|
|
143
|
+
*
|
|
144
|
+
* @param jwks - JSON Web Key Set
|
|
145
|
+
* @returns Imported crypto key for verification
|
|
146
|
+
* @throws Error if no matching key is found
|
|
147
|
+
*/
|
|
148
|
+
async selectKey(jwks) {
|
|
149
|
+
if (!this.id_token) {
|
|
150
|
+
throw new Error('No id_token present');
|
|
151
|
+
}
|
|
152
|
+
// Decode header to get kid and alg
|
|
153
|
+
const header = decodeProtectedHeader(this.id_token);
|
|
154
|
+
if (!header.kid) {
|
|
155
|
+
throw new Error('ID token missing kid in header');
|
|
156
|
+
}
|
|
157
|
+
// Find matching key
|
|
158
|
+
const matchingKey = jwks.keys.find((key) => {
|
|
159
|
+
// Match by kid (required)
|
|
160
|
+
if (key.kid !== header.kid) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
// Match by alg if present in key
|
|
164
|
+
if (key.alg && key.alg !== header.alg) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// Match by use if present (should be 'sig' for signing)
|
|
168
|
+
if (key.use && key.use !== 'sig') {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
if (!matchingKey) {
|
|
174
|
+
throw new Error(`No matching key found in JWKS for kid: ${header.kid}, alg: ${header.alg}`);
|
|
175
|
+
}
|
|
176
|
+
// Import the JWK for verification
|
|
177
|
+
return importJWK(matchingKey, header.alg);
|
|
178
|
+
}
|
|
179
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connectid-tools/rp-nodejs-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Digital Identity Relying Party Node SDK",
|
|
5
5
|
"main": "relying-party-client-sdk.js",
|
|
6
6
|
"types": "relying-party-client-sdk.d.ts",
|
|
@@ -31,17 +31,16 @@
|
|
|
31
31
|
"homepage": "https://github.com/connectid-tools/rp-nodejs-sample-app",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"https": "^1.0.0",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
34
|
+
"jose": "^6.0.0",
|
|
35
|
+
"undici": "^7.16.0",
|
|
36
36
|
"winston": "^3.17.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^20.19.9",
|
|
40
|
-
"@types/openid-client": "^3.7.0",
|
|
41
40
|
"add-js-extension": "^1.0.4",
|
|
42
41
|
"eslint": "^9.32.0",
|
|
43
42
|
"prettier": "^3.6.2",
|
|
44
|
-
"replace-in-files-cli": "^
|
|
43
|
+
"replace-in-files-cli": "^4.0.0",
|
|
45
44
|
"tsx": "^4.20.3",
|
|
46
45
|
"typescript": "^5.9.2"
|
|
47
46
|
},
|
|
@@ -1,37 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AuthorisationServer, ConsolidatedTokenSet, Participant, RelyingPartyClientSdkConfig } from './types';
|
|
1
|
+
import { CallbackParams, ConsolidatedTokenSet, Participant, RelyingPartyClientSdkConfig } from './types.js';
|
|
3
2
|
export default class RelyingPartyClientSdk {
|
|
4
3
|
private readonly logger;
|
|
5
4
|
private config;
|
|
6
|
-
private readonly transportKey;
|
|
7
|
-
private readonly transportPem;
|
|
8
|
-
private readonly signingKey;
|
|
9
|
-
private readonly caPem;
|
|
10
5
|
private readonly purpose;
|
|
11
|
-
private
|
|
12
|
-
private readonly
|
|
13
|
-
private
|
|
14
|
-
private
|
|
6
|
+
private readonly participantsEndpoint;
|
|
7
|
+
private readonly pushedAuthorisationRequestEndpoint;
|
|
8
|
+
private readonly retrieveTokenEndpoint;
|
|
9
|
+
private readonly userInfoEndpoint;
|
|
10
|
+
private readonly httpClient;
|
|
11
|
+
private readonly jwtHelper;
|
|
15
12
|
constructor(config: RelyingPartyClientSdkConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Get the list of participating identity providers within the scheme.
|
|
15
|
+
*
|
|
16
|
+
* Applies filtering based on SDK configuration.
|
|
17
|
+
*
|
|
18
|
+
* @returns List of participants
|
|
19
|
+
*/
|
|
16
20
|
getParticipants(): Promise<Participant[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Get the list of fallback provider participants.
|
|
23
|
+
*
|
|
24
|
+
* @returns List of fallback provider participants
|
|
25
|
+
*/
|
|
17
26
|
getFallbackProviderParticipants(): Promise<Participant[]>;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Sends a Pushed Authorization Request (PAR).
|
|
29
|
+
*
|
|
30
|
+
* @param authServerId - Authorization server ID
|
|
31
|
+
* @param essentialClaims - Claims that must be provided
|
|
32
|
+
* @param voluntaryClaims - Claims that are optional
|
|
33
|
+
* @param purpose - Purpose string for data sharing
|
|
34
|
+
* @returns Object containing authorization URL and PKCE parameters
|
|
35
|
+
*/
|
|
21
36
|
sendPushedAuthorisationRequest(authServerId: string, essentialClaims: string[], voluntaryClaims?: string[], purpose?: string): Promise<{
|
|
22
37
|
authUrl: string;
|
|
23
|
-
|
|
38
|
+
codeVerifier: string;
|
|
24
39
|
state: string;
|
|
25
40
|
nonce: string;
|
|
26
|
-
xFapiInteractionId:
|
|
41
|
+
xFapiInteractionId: string;
|
|
27
42
|
}>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Retrieves tokens using an authorisation code.
|
|
45
|
+
*
|
|
46
|
+
* @param authorisationServerId - Authorisation server ID
|
|
47
|
+
* @param requestParams - OAuth callback parameters
|
|
48
|
+
* @param codeVerifier - PKCE code verifier from PAR
|
|
49
|
+
* @param state - State parameter from PAR
|
|
50
|
+
* @param nonce - Nonce parameter from PAR
|
|
51
|
+
* @returns Consolidated token set with validated claims
|
|
52
|
+
*/
|
|
53
|
+
retrieveTokens(authorisationServerId: string, requestParams: CallbackParams, codeVerifier: string, state: string, nonce: string): Promise<ConsolidatedTokenSet>;
|
|
54
|
+
/**
|
|
55
|
+
* Retrieves user information from the UserInfo endpoint.
|
|
56
|
+
*
|
|
57
|
+
* @param authorisationServerId - Authorization server ID
|
|
58
|
+
* @param accessToken - Access token
|
|
59
|
+
* @returns UserInfo claims
|
|
60
|
+
*/
|
|
61
|
+
getUserInfo(authorisationServerId: string, accessToken: string): Promise<Record<string, unknown>>;
|
|
62
|
+
/**
|
|
63
|
+
* Gets the current date (for testing purposes).
|
|
64
|
+
*
|
|
65
|
+
* @returns Current date
|
|
66
|
+
*/
|
|
67
|
+
getCurrentDate(): Date;
|
|
37
68
|
}
|