Merge pull request #24874 from backstage/freben/singular-jwks
update the jwks external auth to use singular nouns
This commit is contained in:
@@ -96,29 +96,25 @@ backend:
|
||||
- type: jwks
|
||||
options:
|
||||
url: https://example.com/.well-known/jwks.json
|
||||
issuers:
|
||||
- https://example.com
|
||||
algorithms:
|
||||
- RS256
|
||||
audiences:
|
||||
- example
|
||||
issuer: https://example.com
|
||||
algorithm: RS256
|
||||
audience: example, other-example
|
||||
subjectPrefix: custom-prefix
|
||||
- type: jwks
|
||||
options:
|
||||
url: https://another-example.com/.well-known/jwks.json
|
||||
issuers:
|
||||
- https://example.com
|
||||
issuer: https://example.com
|
||||
```
|
||||
|
||||
The URL should point at an unauthenticated endpoint that returns the JWKS.
|
||||
|
||||
Issuers specifies the issuer(s) of the JWT that the authenticating app will accept.
|
||||
`issuer` specifies the issuer(s) of the JWT that the authenticating app will accept.
|
||||
Passed JWTs must have an `iss` claim which matches one of the specified issuers.
|
||||
|
||||
Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs
|
||||
`algorithm` specifies the algorithm(s) that are used to verify the JWT. The passed JWTs
|
||||
must have been signed using one of the listed algorithms.
|
||||
|
||||
Audiences specify the intended audience(s) of the JWT. The passed JWTs must have an "aud"
|
||||
`audience` specifies the intended audience(s) of the JWT. The passed JWTs must have an "aud"
|
||||
claim that matches one of the audiences specified, or have no audience specified.
|
||||
|
||||
For additional details regarding the JWKS configuration, please consult your authentication
|
||||
|
||||
Vendored
+10
-10
@@ -226,30 +226,30 @@ export interface Config {
|
||||
type: 'jwks';
|
||||
options: {
|
||||
/**
|
||||
* Sets the algorithms that should be used to verify the JWT tokens.
|
||||
* The full URL of the JWKS endpoint.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Sets the algorithm(s) that should be used to verify the JWT tokens.
|
||||
* The passed JWTs must have been signed using one of the listed algorithms.
|
||||
*/
|
||||
algorithms?: string[];
|
||||
algorithm?: string | string[];
|
||||
/**
|
||||
* Sets the issuers that should be used to verify the JWT tokens.
|
||||
* Sets the issuer(s) that should be used to verify the JWT tokens.
|
||||
* Passed JWTs must have an `iss` claim which matches one of the specified issuers.
|
||||
*/
|
||||
issuers?: string[];
|
||||
issuer?: string | string[];
|
||||
/**
|
||||
* Sets the audiences that should be used to verify the JWT tokens.
|
||||
* Sets the audience(s) that should be used to verify the JWT tokens.
|
||||
* The passed JWTs must have an "aud" claim that matches one of the audiences specified,
|
||||
* or have no audience specified.
|
||||
*/
|
||||
audiences?: string[];
|
||||
audience?: string | string[];
|
||||
/**
|
||||
* Sets an optional subject prefix. Passes the subject to called plugins.
|
||||
* Useful for debugging and tracking purposes.
|
||||
*/
|
||||
subjectPrefix?: string;
|
||||
/**
|
||||
* Sets the URL containing the JWKS endpoint.
|
||||
*/
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
Vendored
+156
@@ -17,8 +17,72 @@
|
||||
import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api';
|
||||
import { ExternalTokenHandler } from './ExternalTokenHandler';
|
||||
import { TokenHandler } from './types';
|
||||
import {
|
||||
mockServices,
|
||||
setupRequestMockHandlers,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { SignJWT, exportJWK, generateKeyPair } from 'jose';
|
||||
import { DateTime } from 'luxon';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
// Simplified copy of TokenFactory in @backstage/plugin-auth-backend
|
||||
interface AnyJWK extends Record<string, string> {
|
||||
use: 'sig';
|
||||
alg: string;
|
||||
kid: string;
|
||||
kty: string;
|
||||
}
|
||||
class FakeTokenFactory {
|
||||
private readonly keys = new Array<AnyJWK>();
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
issuer: string;
|
||||
keyDurationSeconds: number;
|
||||
},
|
||||
) {}
|
||||
|
||||
async issueToken(params: {
|
||||
claims: {
|
||||
sub: string;
|
||||
ent?: string[];
|
||||
};
|
||||
}): Promise<string> {
|
||||
const pair = await generateKeyPair('RS256');
|
||||
const publicKey = await exportJWK(pair.publicKey);
|
||||
const kid = uuid();
|
||||
publicKey.kid = kid;
|
||||
this.keys.push(publicKey as AnyJWK);
|
||||
|
||||
const iss = this.options.issuer;
|
||||
const sub = params.claims.sub;
|
||||
const ent = params.claims.ent;
|
||||
const aud = 'backstage';
|
||||
const iat = Math.floor(Date.now() / 1000);
|
||||
const exp = iat + this.options.keyDurationSeconds;
|
||||
|
||||
return new SignJWT({ iss, sub, aud, iat, exp, ent, kid })
|
||||
.setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid })
|
||||
.setIssuer(iss)
|
||||
.setAudience(aud)
|
||||
.setSubject(sub)
|
||||
.setIssuedAt(iat)
|
||||
.setExpirationTime(exp)
|
||||
.sign(pair.privateKey);
|
||||
}
|
||||
|
||||
async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {
|
||||
return { keys: this.keys };
|
||||
}
|
||||
}
|
||||
|
||||
describe('ExternalTokenHandler', () => {
|
||||
const server = setupServer();
|
||||
setupRequestMockHandlers(server);
|
||||
|
||||
it('skips over inner handlers that do not match, and applies plugin restrictions', async () => {
|
||||
const handler1: TokenHandler = {
|
||||
add: jest.fn(),
|
||||
@@ -52,4 +116,96 @@ describe('ExternalTokenHandler', () => {
|
||||
`"This token's access is restricted to plugin(s) 'plugin1'"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('successfully parses known methods', async () => {
|
||||
const legacyKey = randomBytes(24);
|
||||
|
||||
const factory = new FakeTokenFactory({
|
||||
issuer: 'blah',
|
||||
keyDurationSeconds: 100,
|
||||
});
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
'https://example.com/.well-known/jwks.json',
|
||||
async (_, res, ctx) => {
|
||||
const keys = await factory.listPublicKeys();
|
||||
return res(ctx.json(keys));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const handler = ExternalTokenHandler.create({
|
||||
ownPluginId: 'catalog',
|
||||
logger: mockServices.logger.mock(),
|
||||
config: mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
auth: {
|
||||
externalAccess: [
|
||||
{
|
||||
type: 'legacy',
|
||||
options: {
|
||||
secret: legacyKey.toString('base64'),
|
||||
subject: 'legacy-subject',
|
||||
},
|
||||
accessRestrictions: [
|
||||
{ plugin: 'catalog', permission: 'catalog.entity.read' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
options: {
|
||||
token: 'defdefdef',
|
||||
subject: 'static-subject',
|
||||
},
|
||||
accessRestrictions: [
|
||||
{ plugin: 'catalog', permission: 'catalog.entity.read' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'jwks',
|
||||
options: {
|
||||
url: 'https://example.com/.well-known/jwks.json',
|
||||
algorithm: 'RS256',
|
||||
issuer: 'blah',
|
||||
audience: 'backstage',
|
||||
subjectPrefix: 'custom-prefix',
|
||||
},
|
||||
accessRestrictions: [
|
||||
{ plugin: 'catalog', permission: 'catalog.entity.read' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const legacyToken = await new SignJWT({
|
||||
sub: 'backstage-server',
|
||||
exp: DateTime.now().plus({ minutes: 1 }).toUnixInteger(),
|
||||
})
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.sign(legacyKey);
|
||||
|
||||
await expect(handler.verifyToken(legacyToken)).resolves.toEqual({
|
||||
subject: 'legacy-subject',
|
||||
accessRestrictions: { permissionNames: ['catalog.entity.read'] },
|
||||
});
|
||||
|
||||
await expect(handler.verifyToken('defdefdef')).resolves.toEqual({
|
||||
subject: 'static-subject',
|
||||
accessRestrictions: { permissionNames: ['catalog.entity.read'] },
|
||||
});
|
||||
|
||||
const jwksToken = await factory.issueToken({
|
||||
claims: { sub: 'jwks-subject' },
|
||||
});
|
||||
await expect(handler.verifyToken(jwksToken)).resolves.toEqual({
|
||||
subject: 'external:custom-prefix:jwks-subject',
|
||||
accessRestrictions: { permissionNames: ['catalog.entity.read'] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+67
-1
@@ -15,8 +15,74 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { readAccessRestrictionsFromConfig } from './helpers';
|
||||
import {
|
||||
readAccessRestrictionsFromConfig,
|
||||
readStringOrStringArrayFromConfig,
|
||||
} from './helpers';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
|
||||
describe('readStringOrStringArrayFromConfig', () => {
|
||||
it('handles all cases correctly', () => {
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
wrongType: 1,
|
||||
wrongTypeInArray: [1],
|
||||
singleString: 'a',
|
||||
spaceSeparatedString: 'a b c',
|
||||
commaSeparatedString: 'a,b,c',
|
||||
mixedSeparatorsString: 'a b,c ,, d',
|
||||
emptyString: '',
|
||||
emptyArray: [],
|
||||
simpleArray: ['a', 'b', 'c'],
|
||||
arrayWithSeparators: ['a b', 'c,d', 'e'],
|
||||
complexDuplicates: ['a', 'a b', 'a', 'b, a'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
readStringOrStringArrayFromConfig(config, 'wrongType'),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid type in config for key 'wrongType' in 'mock-config', got number, wanted string"`,
|
||||
);
|
||||
expect(() =>
|
||||
readStringOrStringArrayFromConfig(config, 'wrongTypeInArray'),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid type in config for key 'wrongTypeInArray[0]' in 'mock-config', got number, wanted string-array"`,
|
||||
);
|
||||
expect(readStringOrStringArrayFromConfig(config, 'singleString')).toEqual([
|
||||
'a',
|
||||
]);
|
||||
expect(
|
||||
readStringOrStringArrayFromConfig(config, 'spaceSeparatedString'),
|
||||
).toEqual(['a', 'b', 'c']);
|
||||
expect(
|
||||
readStringOrStringArrayFromConfig(config, 'commaSeparatedString'),
|
||||
).toEqual(['a', 'b', 'c']);
|
||||
expect(
|
||||
readStringOrStringArrayFromConfig(config, 'mixedSeparatorsString'),
|
||||
).toEqual(['a', 'b', 'c', 'd']);
|
||||
expect(() =>
|
||||
readStringOrStringArrayFromConfig(config, 'emptyString'),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid type in config for key 'emptyString' in 'mock-config', got empty-string, wanted string"`,
|
||||
);
|
||||
expect(
|
||||
readStringOrStringArrayFromConfig(config, 'emptyArray'),
|
||||
).toBeUndefined();
|
||||
expect(readStringOrStringArrayFromConfig(config, 'simpleArray')).toEqual([
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
]);
|
||||
expect(
|
||||
readStringOrStringArrayFromConfig(config, 'arrayWithSeparators'),
|
||||
).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||
expect(
|
||||
readStringOrStringArrayFromConfig(config, 'complexDuplicates'),
|
||||
).toEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readAccessRestrictionsFromConfig', () => {
|
||||
function r(config: JsonObject) {
|
||||
|
||||
+8
-3
@@ -66,8 +66,10 @@ export function readAccessRestrictionsFromConfig(
|
||||
* splits by comma/space into a string array. Can also validate against a known
|
||||
* set of values. Returns undefined if the key didn't exist or if the array
|
||||
* would have ended up being empty.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function stringOrStringArray<T extends string>(
|
||||
export function readStringOrStringArrayFromConfig<T extends string>(
|
||||
root: Config,
|
||||
key: string,
|
||||
validValues?: readonly T[],
|
||||
@@ -108,7 +110,10 @@ function stringOrStringArray<T extends string>(
|
||||
}
|
||||
|
||||
function readPermissionNames(externalAccessEntryConfig: Config) {
|
||||
return stringOrStringArray(externalAccessEntryConfig, 'permission');
|
||||
return readStringOrStringArrayFromConfig(
|
||||
externalAccessEntryConfig,
|
||||
'permission',
|
||||
);
|
||||
}
|
||||
|
||||
function readPermissionAttributes(externalAccessEntryConfig: Config) {
|
||||
@@ -129,7 +134,7 @@ function readPermissionAttributes(externalAccessEntryConfig: Config) {
|
||||
}
|
||||
}
|
||||
|
||||
const action = stringOrStringArray(config, 'action', [
|
||||
const action = readStringOrStringArrayFromConfig(config, 'action', [
|
||||
'create',
|
||||
'read',
|
||||
'update',
|
||||
|
||||
+69
-31
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { SignJWT, exportJWK, generateKeyPair } from 'jose';
|
||||
@@ -21,13 +22,13 @@ import { setupServer } from 'msw/node';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { JWKSHandler } from './jwks';
|
||||
|
||||
// Simplified copy of TokenFactory in @backstage/plugin-auth-backend
|
||||
interface AnyJWK extends Record<string, string> {
|
||||
use: 'sig';
|
||||
alg: string;
|
||||
kid: string;
|
||||
kty: string;
|
||||
}
|
||||
// Simplified copy of TokenFactory in @backstage/plugin-auth-backend
|
||||
class FakeTokenFactory {
|
||||
private readonly keys = new Array<AnyJWK>();
|
||||
|
||||
@@ -100,10 +101,12 @@ describe('JWKSHandler', () => {
|
||||
|
||||
it('verifies token with valid entry', async () => {
|
||||
const validEntry = {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithms: ['RS256'],
|
||||
issuers: [mockBaseUrl],
|
||||
audiences: ['backstage'],
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithm: 'RS256',
|
||||
issuer: mockBaseUrl,
|
||||
audience: 'backstage',
|
||||
},
|
||||
};
|
||||
const jwksHandler = new JWKSHandler();
|
||||
|
||||
@@ -120,17 +123,21 @@ describe('JWKSHandler', () => {
|
||||
|
||||
it('skips invalid entry and continues verification', async () => {
|
||||
const invalidEntry = {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithms: ['RS256'],
|
||||
issuers: ['fakeIssuer'],
|
||||
audiences: ['fakeAud'],
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithm: 'RS256',
|
||||
issuer: ['fakeIssuer'],
|
||||
audience: ['fakeAud'],
|
||||
},
|
||||
};
|
||||
|
||||
const validEntry = {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithms: ['RS256'],
|
||||
issuers: ['multiple-issuers', mockBaseUrl],
|
||||
audiences: ['multiple-audiences', 'backstage'],
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithm: 'RS256',
|
||||
issuer: ['multiple-issuers', mockBaseUrl],
|
||||
audience: ['multiple-audiences', 'backstage'],
|
||||
},
|
||||
};
|
||||
const jwksHandler = new JWKSHandler();
|
||||
|
||||
@@ -148,17 +155,19 @@ describe('JWKSHandler', () => {
|
||||
|
||||
it('returns undefined if no valid entry found', async () => {
|
||||
const invalidEntry1 = {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithms: ['RS256'],
|
||||
issuers: [mockBaseUrl],
|
||||
audiences: [],
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithm: 'RS256',
|
||||
issuer: 'wrong',
|
||||
},
|
||||
};
|
||||
|
||||
const invalidEntry2 = {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithms: ['HS256'],
|
||||
issuers: [],
|
||||
audiences: ['backstage'],
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithm: ['HS256'],
|
||||
audience: 'wrong',
|
||||
},
|
||||
};
|
||||
const jwksHandler = new JWKSHandler();
|
||||
|
||||
@@ -180,17 +189,21 @@ describe('JWKSHandler', () => {
|
||||
expect(() => {
|
||||
jwksHandler.add(
|
||||
new ConfigReader({
|
||||
url: 'https://exampl e.com/jwks',
|
||||
options: {
|
||||
url: 'https://exampl e.com/jwks',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}).toThrow('Invalid URL');
|
||||
}).toThrow('Illegal JWKS URL, must be a set of non-space characters');
|
||||
expect(() => {
|
||||
jwksHandler.add(
|
||||
new ConfigReader({
|
||||
url: 'https://example.com/jwks\n',
|
||||
options: {
|
||||
url: 'https://example.com/jwks\n',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}).toThrow('Illegal URL, must be a set of non-space characters');
|
||||
}).toThrow('Illegal JWKS URL, must be a set of non-space characters');
|
||||
});
|
||||
|
||||
it('gracefully handles no added tokens', async () => {
|
||||
@@ -200,11 +213,13 @@ describe('JWKSHandler', () => {
|
||||
|
||||
it('uses custom subject prefix if provided', async () => {
|
||||
const validEntry = {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithms: ['RS256'],
|
||||
issuers: [mockBaseUrl],
|
||||
audiences: ['backstage'],
|
||||
subjectPrefix: 'custom-prefix',
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
algorithm: 'RS256',
|
||||
issuer: mockBaseUrl,
|
||||
audience: 'backstage',
|
||||
subjectPrefix: 'custom-prefix',
|
||||
},
|
||||
};
|
||||
const jwksHandler = new JWKSHandler();
|
||||
|
||||
@@ -217,7 +232,30 @@ describe('JWKSHandler', () => {
|
||||
const result = await jwksHandler.verifyToken(token);
|
||||
|
||||
expect(result).toEqual({
|
||||
subject: `external:${validEntry.subjectPrefix}:${mockSubject}`,
|
||||
subject: `external:${validEntry.options.subjectPrefix}:${mockSubject}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('carries over access restrictions', async () => {
|
||||
const jwksHandler = new JWKSHandler();
|
||||
jwksHandler.add(
|
||||
new ConfigReader({
|
||||
options: {
|
||||
url: `${mockBaseUrl}/.well-known/jwks.json`,
|
||||
},
|
||||
accessRestrictions: [{ plugin: 'scaffolder', permission: 'do.it' }],
|
||||
}),
|
||||
);
|
||||
|
||||
const token = await factory.issueToken({ claims: { sub: mockSubject } });
|
||||
|
||||
await expect(jwksHandler.verifyToken(token)).resolves.toEqual({
|
||||
subject: `external:${mockSubject}`,
|
||||
allAccessRestrictions: new Map(
|
||||
Object.entries({
|
||||
scaffolder: { permissionNames: ['do.it'] },
|
||||
}),
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+33
-16
@@ -16,7 +16,11 @@
|
||||
|
||||
import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose';
|
||||
import { Config } from '@backstage/config';
|
||||
import { TokenHandler } from './types';
|
||||
import {
|
||||
readAccessRestrictionsFromConfig,
|
||||
readStringOrStringArrayFromConfig,
|
||||
} from './helpers';
|
||||
import { AccessRestriptionsMap, TokenHandler } from './types';
|
||||
|
||||
/**
|
||||
* Handles `type: jwks` access.
|
||||
@@ -31,20 +35,30 @@ export class JWKSHandler implements TokenHandler {
|
||||
subjectPrefix?: string;
|
||||
url: URL;
|
||||
jwks: JWTVerifyGetKey;
|
||||
allAccessRestrictions?: AccessRestriptionsMap;
|
||||
}> = [];
|
||||
|
||||
add(options: Config) {
|
||||
const algorithms = options.getOptionalStringArray('algorithms');
|
||||
const issuers = options.getOptionalStringArray('issuers');
|
||||
const audiences = options.getOptionalStringArray('audiences');
|
||||
const subjectPrefix = options.getOptionalString('subjectPrefix');
|
||||
const url = new URL(options.getString('url'));
|
||||
const jwks = createRemoteJWKSet(url);
|
||||
|
||||
if (!options.getString('url').match(/^\S+$/)) {
|
||||
throw new Error('Illegal URL, must be a set of non-space characters');
|
||||
add(config: Config) {
|
||||
if (!config.getString('options.url').match(/^\S+$/)) {
|
||||
throw new Error(
|
||||
'Illegal JWKS URL, must be a set of non-space characters',
|
||||
);
|
||||
}
|
||||
|
||||
const algorithms = readStringOrStringArrayFromConfig(
|
||||
config,
|
||||
'options.algorithm',
|
||||
);
|
||||
const issuers = readStringOrStringArrayFromConfig(config, 'options.issuer');
|
||||
const audiences = readStringOrStringArrayFromConfig(
|
||||
config,
|
||||
'options.audience',
|
||||
);
|
||||
const subjectPrefix = config.getOptionalString('options.subjectPrefix');
|
||||
const url = new URL(config.getString('options.url'));
|
||||
const jwks = createRemoteJWKSet(url);
|
||||
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
||||
|
||||
this.#entries.push({
|
||||
algorithms,
|
||||
audiences,
|
||||
@@ -52,6 +66,7 @@ export class JWKSHandler implements TokenHandler {
|
||||
jwks,
|
||||
subjectPrefix,
|
||||
url,
|
||||
allAccessRestrictions,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,11 +82,13 @@ export class JWKSHandler implements TokenHandler {
|
||||
});
|
||||
|
||||
if (sub) {
|
||||
if (entry.subjectPrefix) {
|
||||
return { subject: `external:${entry.subjectPrefix}:${sub}` };
|
||||
}
|
||||
|
||||
return { subject: `external:${sub}` };
|
||||
const prefix = entry.subjectPrefix
|
||||
? `external:${entry.subjectPrefix}:`
|
||||
: 'external:';
|
||||
return {
|
||||
subject: `${prefix}${sub}`,
|
||||
allAccessRestrictions: entry.allAccessRestrictions,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user