Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/SPRIND-89 #177

Merged
merged 29 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9c273b9
feat: added support for first party applications
Brummos Jan 9, 2025
f3b89fc
Merge branch 'develop' into feature/SPRIND-89
Brummos Jan 9, 2025
5a0dba4
chore: cleanup
Brummos Jan 9, 2025
bf1217f
chore: cleanup
Brummos Jan 9, 2025
ce3ef0b
chore: typo fix
Brummos Jan 9, 2025
2b4c069
chore: specifically check for true in isAuthorizationChallengeEndpoin…
Brummos Jan 9, 2025
acf2cdc
chore: added acquiring authorization challenge authorization code to …
Brummos Jan 13, 2025
58af8fe
chore: cleanup
Brummos Jan 13, 2025
b97ff27
chore: make opts optional as client id is default from the client
Brummos Jan 13, 2025
3a426b5
chore: use authorization_code for acquiring the access token
Brummos Jan 13, 2025
1825b96
chore: cleanup
Brummos Jan 13, 2025
e8dca63
chore: refactor authorization challenge code error handling
Brummos Jan 13, 2025
3e2c8d7
chore: small fixes
Brummos Jan 15, 2025
084d916
chore: fixes
Brummos Jan 15, 2025
ba107cc
chore: added tests
Brummos Jan 15, 2025
1eef34b
chore: added tests
Brummos Jan 15, 2025
c6c5df2
chore: added tests
Brummos Jan 15, 2025
a8f712d
chore: fixes
Brummos Jan 15, 2025
4a74802
chore: fix test
Brummos Jan 15, 2025
92430bc
chore: cleanup
Brummos Jan 15, 2025
9b61ea6
chore: for first party flow use presentation id from issuer options
Brummos Jan 17, 2025
1989813
chore: extract IEndpointOpts for agent project
sanderPostma Jan 17, 2025
908b2e7
chore: use auth server endpoint when available
Brummos Jan 21, 2025
bfb52ee
Merge branch 'feature/SPRIND-89' of https://github.com/Sphereon-Opens…
Brummos Jan 21, 2025
5c4b66e
chore: addressing PR comments
Brummos Jan 22, 2025
d416e00
chore: fix test url
Brummos Jan 22, 2025
8eca880
chore: removed first party flag
Brummos Jan 22, 2025
fa16634
chore: revert isFirstParty flag
Brummos Jan 22, 2025
147da96
Merge branch 'develop' into feature/SPRIND-89
Brummos Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 91 additions & 2 deletions packages/client/lib/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {
AuthorizationChallengeCodeResponse,
AuthorizationChallengeRequestOpts,
AuthorizationDetails,
AuthorizationRequestOpts,
CodeChallengeMethod,
CommonAuthorizationChallengeRequest,
convertJsonToURI,
CreateRequestObjectMode,
CredentialConfigurationSupportedV1_0_13,
Expand All @@ -10,20 +13,24 @@
CredentialOfferPayloadV1_0_13,
CredentialOfferRequestWithBaseUrl,
determineSpecVersionFromOffer,
EndpointMetadata,
EndpointMetadataResultV1_0_13,
formPost,
IssuerOpts,
isW3cCredentialSupported,
JsonURIMode,
Jwt,
OpenId4VCIVersion,
OpenIDResponse,
PARMode,
PKCEOpts,
PushedAuthorizationResponse,
RequestObjectOpts,
ResponseType,
} from '@sphereon/oid4vci-common';
ResponseType
} from '@sphereon/oid4vci-common'
import Debug from 'debug';

import { MetadataClient } from './MetadataClient'
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';

const debug = Debug('sphereon:oid4vci');
Expand Down Expand Up @@ -272,3 +279,85 @@
}
return authorizationDetails;
};

export const acquireAuthorizationChallengeAuthCode = async (opts: AuthorizationChallengeRequestOpts): Promise<OpenIDResponse<AuthorizationChallengeCodeResponse>> => {
const { metadata } = opts

const issuer = opts.credentialIssuer ?? opts?.metadata?.issuer as string
if (!issuer) {
throw Error('Issuer required at this point');

Check warning on line 288 in packages/client/lib/AuthorizationCodeClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/AuthorizationCodeClient.ts#L288

Added line #L288 was not covered by tests
}

const issuerOpts = {
issuer,
}

return await acquireAuthorizationChallengeAuthCodeUsingRequest({
authorizationChallengeRequest: await createAuthorizationChallengeRequest(opts),
metadata,
issuerOpts
});
}

export const acquireAuthorizationChallengeAuthCodeUsingRequest = async (
opts: {
authorizationChallengeRequest: CommonAuthorizationChallengeRequest,
metadata?: EndpointMetadata,
issuerOpts?: IssuerOpts
}
): Promise<OpenIDResponse<AuthorizationChallengeCodeResponse>> => {
const { authorizationChallengeRequest, issuerOpts } = opts
const metadata = opts?.metadata
? opts?.metadata
: issuerOpts?.fetchMetadata
? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
: undefined

Check warning on line 314 in packages/client/lib/AuthorizationCodeClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/AuthorizationCodeClient.ts#L313-L314

Added lines #L313 - L314 were not covered by tests
const authorizationChallengeCodeUrl = metadata?.authorization_challenge_endpoint

if (!authorizationChallengeCodeUrl) {
return Promise.reject(Error('Cannot determine authorization challenge endpoint URL'))

Check warning on line 318 in packages/client/lib/AuthorizationCodeClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/AuthorizationCodeClient.ts#L318

Added line #L318 was not covered by tests
}

const response = await sendAuthorizationChallengeRequest(
authorizationChallengeCodeUrl,
authorizationChallengeRequest
);

return response
}

export const createAuthorizationChallengeRequest = async (opts: AuthorizationChallengeRequestOpts): Promise<CommonAuthorizationChallengeRequest> => {
const {
clientId,
issuerState,
authSession,
scope,
definitionId,
codeChallenge,
codeChallengeMethod,
presentationDuringIssuanceSession
} = opts;

const request: CommonAuthorizationChallengeRequest = {
client_id: clientId,
issuer_state: issuerState,
auth_session: authSession,
scope,
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
definition_id: definitionId,
presentation_during_issuance_session: presentationDuringIssuanceSession
}

return request
}

export const sendAuthorizationChallengeRequest = async (
authorizationChallengeCodeUrl: string,
authorizationChallengeRequest: CommonAuthorizationChallengeRequest,
opts?: { headers?: Record<string, string> }
): Promise<OpenIDResponse<AuthorizationChallengeCodeResponse>> => {
return await formPost(authorizationChallengeCodeUrl, convertJsonToURI(authorizationChallengeRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), {
customHeaders: opts?.headers ? opts.headers : undefined,
});
}
12 changes: 11 additions & 1 deletion packages/client/lib/MetadataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
let credential_endpoint: string | undefined;
let deferred_credential_endpoint: string | undefined;
let authorization_endpoint: string | undefined;
let authorization_challenge_endpoint: string | undefined;
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
let authorization_servers: string[] | undefined = [issuer];
let authorization_server: string | undefined = undefined;
Expand Down Expand Up @@ -130,8 +131,16 @@
);
}
authorization_endpoint = authMetadata.authorization_endpoint;
if (!authMetadata.authorization_challenge_endpoint) {
console.warn(`Authorization Server ${authorization_challenge_endpoint} did not provide a authorization_challenge_endpoint`);
} else if (authorization_challenge_endpoint && authMetadata.authorization_challenge_endpoint !== authorization_challenge_endpoint) {
nklomp marked this conversation as resolved.
Show resolved Hide resolved
throw Error(

Check warning on line 137 in packages/client/lib/MetadataClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/MetadataClient.ts#L137

Added line #L137 was not covered by tests
`Credential issuer has a different authorization_challenge_endpoint (${authorization_challenge_endpoint}) from the Authorization Server (${authMetadata.authorization_challenge_endpoint})`,
);
}
authorization_challenge_endpoint = authMetadata.authorization_challenge_endpoint;
if (!authMetadata.token_endpoint) {
throw Error(`Authorization Sever ${authorization_servers} did not provide a token_endpoint`);
throw Error(`Authorization Server ${authorization_servers} did not provide a token_endpoint`);
} else if (token_endpoint && authMetadata.token_endpoint !== token_endpoint) {
throw Error(
`Credential issuer has a different token_endpoint (${token_endpoint}) from the Authorization Server (${authMetadata.token_endpoint})`,
Expand Down Expand Up @@ -193,6 +202,7 @@
deferred_credential_endpoint,
...(authorization_server ? { authorization_server } : { authorization_servers: authorization_servers }),
authorization_endpoint,
authorization_challenge_endpoint,
authorizationServerType,
credentialIssuerMetadata: authorization_server
? (credentialIssuerMetadata as IssuerMetadataV1_0_08 & Partial<AuthorizationServerMetadata>)
Expand Down
12 changes: 11 additions & 1 deletion packages/client/lib/MetadataClientV1_0_11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
let credential_endpoint: string | undefined;
let deferred_credential_endpoint: string | undefined;
let authorization_endpoint: string | undefined;
let authorization_challenge_endpoint: string | undefined;
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
let authorization_server: string = issuer;
const oid4vciResponse = await MetadataClientV1_0_11.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
Expand Down Expand Up @@ -105,8 +106,16 @@
);
}
authorization_endpoint = authMetadata.authorization_endpoint;
if (!authMetadata.authorization_challenge_endpoint) {
BtencateSphereon marked this conversation as resolved.
Show resolved Hide resolved
console.warn(`Authorization Server ${authorization_challenge_endpoint} did not provide a authorization_challenge_endpoint`);
} else if (authorization_challenge_endpoint && authMetadata.authorization_challenge_endpoint !== authorization_challenge_endpoint) {
throw Error(

Check warning on line 112 in packages/client/lib/MetadataClientV1_0_11.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/MetadataClientV1_0_11.ts#L112

Added line #L112 was not covered by tests
`Credential issuer has a different authorization_challenge_endpoint (${authorization_challenge_endpoint}) from the Authorization Server (${authMetadata.authorization_challenge_endpoint})`,
);
}
authorization_challenge_endpoint = authMetadata.authorization_challenge_endpoint;
if (!authMetadata.token_endpoint) {
throw Error(`Authorization Sever ${authorization_server} did not provide a token_endpoint`);
throw Error(`Authorization Server ${authorization_server} did not provide a token_endpoint`);

Check warning on line 118 in packages/client/lib/MetadataClientV1_0_11.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/MetadataClientV1_0_11.ts#L118

Added line #L118 was not covered by tests
} else if (token_endpoint && authMetadata.token_endpoint !== token_endpoint) {
throw Error(
`Credential issuer has a different token_endpoint (${token_endpoint}) from the Authorization Server (${authMetadata.token_endpoint})`,
Expand Down Expand Up @@ -165,6 +174,7 @@
deferred_credential_endpoint,
authorization_server,
authorization_endpoint,
authorization_challenge_endpoint,
authorizationServerType,
credentialIssuerMetadata: credentialIssuerMetadata as unknown as Partial<AuthorizationServerMetadata> & IssuerMetadataV1_0_08,
authorizationServerMetadata: authMetadata,
Expand Down
12 changes: 11 additions & 1 deletion packages/client/lib/MetadataClientV1_0_13.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
let credential_endpoint: string | undefined;
let deferred_credential_endpoint: string | undefined;
let authorization_endpoint: string | undefined;
let authorization_challenge_endpoint: string | undefined;
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
let authorization_servers: string[] = [issuer];
const oid4vciResponse = await MetadataClientV1_0_13.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
Expand Down Expand Up @@ -104,8 +105,16 @@
);
}
authorization_endpoint = authMetadata.authorization_endpoint;
if (!authMetadata.authorization_challenge_endpoint) {
nklomp marked this conversation as resolved.
Show resolved Hide resolved
console.warn(`Authorization Server ${authorization_challenge_endpoint} did not provide a authorization_challenge_endpoint`);
} else if (authorization_challenge_endpoint && authMetadata.authorization_challenge_endpoint !== authorization_challenge_endpoint) {
throw Error(

Check warning on line 111 in packages/client/lib/MetadataClientV1_0_13.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/MetadataClientV1_0_13.ts#L111

Added line #L111 was not covered by tests
`Credential issuer has a different authorization_challenge_endpoint (${authorization_challenge_endpoint}) from the Authorization Server (${authMetadata.authorization_challenge_endpoint})`,
);
}
authorization_challenge_endpoint = authMetadata.authorization_challenge_endpoint;
if (!authMetadata.token_endpoint) {
throw Error(`Authorization Sever ${authorization_servers} did not provide a token_endpoint`);
throw Error(`Authorization Server ${authorization_servers} did not provide a token_endpoint`);

Check warning on line 117 in packages/client/lib/MetadataClientV1_0_13.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/MetadataClientV1_0_13.ts#L117

Added line #L117 was not covered by tests
} else if (token_endpoint && authMetadata.token_endpoint !== token_endpoint) {
throw Error(
`Credential issuer has a different token_endpoint (${token_endpoint}) from the Authorization Server (${authMetadata.token_endpoint})`,
Expand Down Expand Up @@ -164,6 +173,7 @@
deferred_credential_endpoint,
authorization_server: authorization_servers[0],
authorization_endpoint,
authorization_challenge_endpoint,
authorizationServerType,
credentialIssuerMetadata: credentialIssuerMetadata,
authorizationServerMetadata: authMetadata,
Expand Down
63 changes: 52 additions & 11 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
AccessTokenRequestOpts,
AccessTokenResponse,
Alg,
AuthorizationChallengeCodeResponse,
AuthorizationChallengeErrorResponse,
AuthorizationChallengeRequestOpts,
AuthorizationRequestOpts,
AuthorizationResponse,
AuthorizationServerOpts,
Expand Down Expand Up @@ -33,14 +36,17 @@
OpenId4VCIVersion,
PKCEOpts,
ProofOfPossessionCallbacks,
toAuthorizationResponsePayload,
} from '@sphereon/oid4vci-common';
toAuthorizationResponsePayload
} from '@sphereon/oid4vci-common'
import { CredentialFormat } from '@sphereon/ssi-types';
import Debug from 'debug';

import { AccessTokenClient } from './AccessTokenClient';
import { AccessTokenClientV1_0_11 } from './AccessTokenClientV1_0_11';
import { createAuthorizationRequestUrl } from './AuthorizationCodeClient';
import {
acquireAuthorizationChallengeAuthCode,
createAuthorizationRequestUrl
} from './AuthorizationCodeClient'
import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11';
import { CredentialOfferClient } from './CredentialOfferClient';
import { CredentialRequestOpts } from './CredentialRequestClient';
Expand Down Expand Up @@ -89,7 +95,7 @@
endpointMetadata?: EndpointMetadataResult;
accessTokenResponse?: AccessTokenResponse;
authorizationRequestOpts?: AuthorizationRequestOpts;
authorizationCodeResponse?: AuthorizationResponse;
authorizationCodeResponse?: AuthorizationResponse | AuthorizationChallengeCodeResponse;
authorizationURL?: string;
}) {
const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined);
Expand Down Expand Up @@ -270,21 +276,37 @@
this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce });
}

public async acquireAuthorizationChallengeCode(opts?: AuthorizationChallengeRequestOpts): Promise<AuthorizationChallengeCodeResponse> {
const response = await acquireAuthorizationChallengeAuthCode({
metadata: this.endpointMetadata,
credentialIssuer: this.getIssuer(),
clientId: this._state.clientId ?? this._state.authorizationRequestOpts?.clientId,
...opts
})

if (response.errorBody) {
debug(`Authorization code error:\r\n${JSON.stringify(response.errorBody)}`);
const error = response.errorBody as AuthorizationChallengeErrorResponse
return Promise.reject(error)
} else if (!response.successBody) {
debug(`Authorization code error. No success body`);
return Promise.reject(Error(`Retrieving an authorization code token from ${this._state.endpointMetadata?.authorization_challenge_endpoint} for issuer ${this.getIssuer()} failed as there was no success response body`))
}

return { ...response.successBody }
}

Check warning on line 298 in packages/client/lib/OpenID4VCIClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/OpenID4VCIClient.ts#L298

Added line #L298 was not covered by tests
public async acquireAccessToken(
opts?: Omit<AccessTokenRequestOpts, 'credentialOffer' | 'credentialIssuer' | 'metadata' | 'additionalParams'> & {
clientId?: string;
authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
authorizationResponse?: string | AuthorizationResponse | AuthorizationChallengeCodeResponse; // Pass in an auth response, either as URI/redirect, or object
additionalRequestParams?: Record<string, any>;
},
): Promise<AccessTokenResponse & { params?: DPoPResponseParams }> {
const { pin, clientId = this._state.clientId ?? this._state.authorizationRequestOpts?.clientId } = opts ?? {};
let { redirectUri } = opts ?? {};
if (opts?.authorizationResponse) {
this._state.authorizationCodeResponse = { ...toAuthorizationResponsePayload(opts.authorizationResponse) };
} else if (opts?.code) {
this._state.authorizationCodeResponse = { code: opts.code };
}
const code = this._state.authorizationCodeResponse?.code;

const code = this.getAuthorizationCode(opts?.authorizationResponse, opts?.code)

if (opts?.codeVerifier) {
this._state.pkce.codeVerifier = opts.codeVerifier;
Expand Down Expand Up @@ -654,6 +676,15 @@
return this.endpointMetadata ? this.endpointMetadata.credential_endpoint : `${this.getIssuer()}/credential`;
}

public getAuthorizationChallengeEndpoint(): string | undefined {
this.assertIssuerData();
return this.endpointMetadata?.authorization_challenge_endpoint;
}

public hasAuthorizationChallengeEndpoint(): boolean {
return !!this.getAuthorizationChallengeEndpoint();
}

Check warning on line 686 in packages/client/lib/OpenID4VCIClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/OpenID4VCIClient.ts#L685-L686

Added lines #L685 - L686 were not covered by tests

public hasDeferredCredentialEndpoint(): boolean {
return !!this.getAccessTokenEndpoint();
}
Expand Down Expand Up @@ -727,4 +758,14 @@
authorizationRequestOpts.clientId = clientId;
return authorizationRequestOpts;
}

private getAuthorizationCode = (authorizationResponse?: string | AuthorizationResponse | AuthorizationChallengeCodeResponse, code?: string): string | undefined => {
if (authorizationResponse) {
this._state.authorizationCodeResponse = { ...toAuthorizationResponsePayload(authorizationResponse) };
} else if (code) {
this._state.authorizationCodeResponse = { code };
}

return (this._state.authorizationCodeResponse as AuthorizationResponse)?.code ?? (this._state.authorizationCodeResponse as AuthorizationChallengeCodeResponse)?.authorization_code;
}

Check warning on line 770 in packages/client/lib/OpenID4VCIClient.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/lib/OpenID4VCIClient.ts#L770

Added line #L770 was not covered by tests
}
Loading
Loading