Skip to content

Commit 0533c03

Browse files
committed
feat: Generalize JWKS usage
1 parent 40accde commit 0533c03

20 files changed

Lines changed: 177 additions & 616 deletions

File tree

packages/css/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ export * from './util/fetch/Fetcher';
2828
export * from './util/fetch/BaseFetcher';
2929
export * from './util/fetch/PausableFetcher';
3030
export * from './util/fetch/RetryingFetcher';
31-
export * from './util/fetch/SignedFetcher';
3231
export * from './util/fetch/StatusDependant';
3332
export * from './util/fetch/StatusDependantServerConfigurator';

packages/css/src/util/fetch/SignedFetcher.ts

Lines changed: 0 additions & 73 deletions
This file was deleted.

packages/css/test/unit/util/fetch/SignedFetcher.test.ts

Lines changed: 0 additions & 73 deletions
This file was deleted.

packages/uma/config/credentials/validators/http-message.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

packages/uma/config/credentials/verifiers/default.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"TypedVerifier:_verifiers_value": {
3333
"@id": "urn:uma:default:OidcVerifier",
3434
"@type": "OidcVerifier",
35-
"baseUrl": { "@id": "urn:uma:variables:baseUrl" },
3635
"derivationStore": { "@id": "urn:uma:default:DerivationStore" }
3736
}
3837
},

packages/uma/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
"@types/n3": "^1.16.4",
6767
"asynchronous-handlers": "^1.0.2",
6868
"componentsjs": "^6.3.0",
69-
"get-jwks": "^9.0.1",
7069
"global-logger-factory": "^1.0.0",
7170
"http-message-signatures": "^1.0.4",
7271
"jose": "^5.2.2",

packages/uma/src/credentials/Formats.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export const UNSECURE = 'urn:solidlab:uma:claims:formats:webid';
44
export const OIDC = 'http://openid.net/specs/openid-connect-core-1_0.html#IDToken';
55
export const ACCESS_TOKEN = 'urn:ietf:params:oauth:token-type:access_token';
66
export const REFRESH_TOKEN = 'urn:ietf:params:oauth:token-type:refresh_token';
7+
export const VC_JWT = 'application/vc+jwt';

packages/uma/src/credentials/verify/JwtVerifier.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import buildGetJwks, { GetJwks } from 'get-jwks';
21
import { getLoggerFor } from 'global-logger-factory';
3-
import { decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';
2+
import { decodeJwt, jwtVerify } from 'jose';
3+
import { getJwks } from '../../util/JwtUtil';
44
import { ClaimSet } from '../ClaimSet';
55
import { Credential } from '../Credential';
66
import { JWT } from '../Formats';
@@ -12,7 +12,6 @@ import { Verifier } from './Verifier';
1212
*/
1313
export class JwtVerifier implements Verifier {
1414
protected readonly logger = getLoggerFor(this);
15-
protected jwks:GetJwks = buildGetJwks();
1615

1716
constructor(
1817
private readonly allowedClaims: string[],
@@ -34,23 +33,8 @@ export class JwtVerifier implements Verifier {
3433
throw new Error(`JWT should contain 'iss' claim.`);
3534
}
3635

37-
const params = decodeProtectedHeader(credential.token);
38-
39-
if (!params.alg) {
40-
throw new Error(`JWT should contain 'alg' header.`);
41-
}
42-
43-
if (!params.kid) {
44-
throw new Error(`JWT should contain 'kid' header.`);
45-
}
46-
47-
const jwk = await this.jwks.getJwk({
48-
domain: claims.iss,
49-
alg: params.alg,
50-
kid: params.kid,
51-
});
52-
53-
await jwtVerify(credential.token, Object.assign(jwk, { type: 'JWK' }));
36+
const jwkSet = await getJwks(claims.iss);
37+
await jwtVerify(credential.token, jwkSet);
5438
}
5539

5640
for (const claim of Object.keys(claims)) {

packages/uma/src/credentials/verify/OidcVerifier.ts

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
2-
import {
3-
BadRequestHttpError,
4-
ForbiddenHttpError,
5-
InternalServerError,
6-
joinUrl,
7-
KeyValueStorage
8-
} from '@solid/community-server';
2+
import { BadRequestHttpError, ForbiddenHttpError, InternalServerError, KeyValueStorage } from '@solid/community-server';
93
import { getLoggerFor } from 'global-logger-factory';
10-
import { createRemoteJWKSet, decodeJwt, JWTPayload, jwtVerify } from 'jose';
4+
import { decodeJwt, jwtVerify } from 'jose';
115
import { AccessToken } from '../../tokens/AccessToken';
126
import { UMA_SCOPES } from '../../ucp/util/Vocabularies';
7+
import { getJwks } from '../../util/JwtUtil';
138
import { reType } from '../../util/ReType';
149
import { Permission } from '../../views/Permission';
1510
import { ACCESS, CLIENTID, WEBID } from '../Claims';
@@ -21,19 +16,16 @@ import { Verifier } from './Verifier';
2116
/**
2217
* A Verifier for OIDC Tokens.
2318
*
24-
* The `allowedIssuers` list can be used to only allow tokens from these issuers.
25-
* Default is an empty list, which allows all issuers.
19+
* To only allow tokens from certain issuers, set `verifyOptions` to { issuer: [ 'http://example.com/' ] }.
2620
*/
2721
export class OidcVerifier implements Verifier {
2822
protected readonly logger = getLoggerFor(this);
2923

3024
private readonly verifyToken = createSolidTokenVerifier();
3125

3226
public constructor(
33-
protected readonly baseUrl: string,
3427
protected readonly derivationStore: KeyValueStorage<string, string>,
35-
protected readonly allowedIssuers: string[] = [],
36-
protected readonly verifyOptions: Record<string, unknown> = {},
28+
protected readonly verifyOptions: Record<string, unknown> = {}, // JWTVerifyOptions
3729
) {}
3830

3931
/** @inheritdoc */
@@ -46,11 +38,10 @@ export class OidcVerifier implements Verifier {
4638
// We first need to determine if this is a Solid OIDC token or a standard one
4739
const unsafeDecoded = decodeJwt(credential.token);
4840
const isSolidToken = (unsafeDecoded.aud === 'solid' ||
49-
(Array.isArray(unsafeDecoded.aud) && unsafeDecoded.aud.includes('solid')))
41+
(Array.isArray(unsafeDecoded.aud) && unsafeDecoded.aud.includes('solid')))
5042
&& typeof unsafeDecoded.webid === 'string';
5143

5244
try {
53-
this.validateToken(unsafeDecoded);
5445
if (isSolidToken) {
5546
return await this.verifySolidToken(credential.token);
5647
} else {
@@ -64,18 +55,13 @@ export class OidcVerifier implements Verifier {
6455
}
6556
}
6657

67-
protected validateToken(payload: JWTPayload): void {
68-
// TODO: disable audience check for now, need to investigate required values further
69-
// if (payload.aud !== this.baseUrl && !(Array.isArray(payload.aud) && payload.aud.includes(this.baseUrl))) {
70-
// throw new BadRequestHttpError('This server is not valid audience for the token');
71-
// }
72-
if (!payload.iss || this.allowedIssuers.length > 0 && !this.allowedIssuers.includes(payload.iss)) {
73-
throw new BadRequestHttpError('Unsupported issuer');
74-
}
75-
}
76-
7758
protected async verifySolidToken(token: string): Promise<{ [WEBID]: string, [CLIENTID]?: string }> {
7859
const claims = await this.verifyToken(`Bearer ${token}`);
60+
const issuers = this.verifyOptions.issuer;
61+
const allowedIssuers = issuers !== undefined && (typeof issuers === 'string' ? [issuers] : issuers as string[]);
62+
if (!claims.iss || (allowedIssuers && !allowedIssuers.includes(claims.iss))) {
63+
throw new BadRequestHttpError('Unsupported issuer');
64+
}
7965
// Depends on the spec version which field to use
8066
const clientId = (claims as { azp?: string }).azp ?? claims.client_id;
8167

@@ -91,16 +77,7 @@ export class OidcVerifier implements Verifier {
9177

9278
protected async verifyStandardToken(token: string, format: string, issuer: string):
9379
Promise<{ [WEBID]?: string, [CLIENTID]?: string, [ACCESS]?: Permission[] }> {
94-
const configUrl = joinUrl(issuer, '/.well-known/openid-configuration');
95-
const configResponse = await fetch(configUrl);
96-
if (configResponse.status !== 200) {
97-
throw new BadRequestHttpError(`Unable to access ${configUrl}`);
98-
}
99-
const config = await configResponse.json() as { jwks_uri?: string };
100-
if (!config.jwks_uri) {
101-
throw new BadRequestHttpError(`Missing jwks_uri from ${configUrl}`);
102-
}
103-
const jwkSet = createRemoteJWKSet(new URL(config.jwks_uri));
80+
const jwkSet = await getJwks(issuer);
10481
const decoded = await jwtVerify(token, jwkSet, this.verifyOptions);
10582

10683
if (format === OIDC) {

packages/uma/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ export * from './util/http/server/JsonHttpErrorHandler';
8787
export * from './util/http/server/JsonFormHttpHandler';
8888
export * from './util/http/server/NodeHttpRequestResponseHandler';
8989
export * from './util/http/server/RoutedHttpRequestHandler';
90-
export * from './util/http/validate/HttpMessageValidator';
9190
export * from './util/http/validate/PatRequestValidator';
9291
export * from './util/http/validate/RequestValidator';
9392

@@ -107,7 +106,7 @@ export * from './ucp/util/Vocabularies';
107106
// Util
108107
export * from './util/AggregatorUtil';
109108
export * from './util/ConvertUtil';
110-
export * from './util/HttpMessageSignatures';
109+
export * from './util/JwtUtil';
111110
export * from './util/RegistrationStore';
112111
export * from './util/Result';
113112
export * from './util/ReType';

0 commit comments

Comments
 (0)