diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index e678c82477..0b2aa369ec 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -103,6 +103,7 @@ backend: - RS256 audiences: - example + subjectPrefix: custom-prefix - type: jwks options: url: https://another-example.com/.well-known/jwks.json @@ -125,7 +126,9 @@ For additional details regarding the JWKS configuration, please consult your aut provider's documentation. The subject returned from the token verification will become part of the -credentials object that the request recipient plugins get. +credentials object that the request recipient plugins get. All subjects will have the prefix +`external:`, but you can also provide a custom subjectPrefix which will get appended before the +subject returned from your JWKS service (ex. `external:custom-prefix:sub`). ## Legacy Tokens diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index dd3df07435..d734cbf984 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -25,29 +25,31 @@ import { TokenHandler } from './types'; */ export class JWKSHandler implements TokenHandler { #entries: Array<{ - algorithms: string[] | undefined; - audiences: string[] | undefined; - issuers: string[] | undefined; - url: string; + algorithms?: string[]; + audiences?: string[]; + issuers?: string[]; + subjectPrefix?: string; + url: URL; }> = []; add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms'); const issuers = options.getOptionalStringArray('issuers'); const audiences = options.getOptionalStringArray('audiences'); - const url = options.getString('url'); + const subjectPrefix = options.getOptionalString('subjectPrefix'); + const url = new URL(options.getString('url')); - if (!url.match(/^\S+$/)) { + if (!options.getString('url').match(/^\S+$/)) { throw new Error('Illegal URL, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, url }); + this.#entries.push({ algorithms, audiences, issuers, subjectPrefix, url }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(new URL(entry.url)); + const jwks = createRemoteJWKSet(entry.url); const { payload: { sub }, } = await jwtVerify(token, jwks, { @@ -57,7 +59,11 @@ export class JWKSHandler implements TokenHandler { }); if (sub) { - return { subject: sub }; + if (entry.subjectPrefix) { + return { subject: `external:${entry.subjectPrefix}:${sub}` }; + } + + return { subject: `external:${sub}` }; } } catch { continue;