feat: first attempt at adding jwks-auth to external token handlers
Signed-off-by: Ryan Hanchett <ryan.hanchett@invitae.com>
This commit is contained in:
Vendored
+3
@@ -21,6 +21,7 @@ import {
|
||||
import { LegacyTokenHandler } from './legacy';
|
||||
import { StaticTokenHandler } from './static';
|
||||
import { TokenHandler } from './types';
|
||||
import { JWKSHandler } from './jwks';
|
||||
|
||||
const NEW_CONFIG_KEY = 'backend.auth.externalAccess';
|
||||
const OLD_CONFIG_KEY = 'backend.auth.keys';
|
||||
@@ -40,9 +41,11 @@ export class ExternalTokenHandler {
|
||||
|
||||
const staticHandler = new StaticTokenHandler();
|
||||
const legacyHandler = new LegacyTokenHandler();
|
||||
const jwksHandler = new JWKSHandler();
|
||||
const handlers: Record<string, TokenHandler> = {
|
||||
static: staticHandler,
|
||||
legacy: legacyHandler,
|
||||
jwks: jwksHandler,
|
||||
};
|
||||
|
||||
// Load the new-style handlers
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { jwtVerify, createRemoteJWKSet } from 'jose';
|
||||
import { Config } from '@backstage/config';
|
||||
import { TokenHandler } from './types';
|
||||
|
||||
/**
|
||||
* Handles `type: jwks` access.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class JWKSHandler implements TokenHandler {
|
||||
#entries: Array<{
|
||||
algorithms: string[];
|
||||
audiences: string[];
|
||||
issuers: string[];
|
||||
uri: string;
|
||||
}> = [];
|
||||
|
||||
add(options: Config) {
|
||||
const algorithms = options.getOptionalStringArray('algorithms') ?? [];
|
||||
const issuers = options.getOptionalStringArray('issuers') ?? [];
|
||||
const audiences = options.getOptionalStringArray('audiences') ?? [];
|
||||
const uri = options.getString('uri');
|
||||
|
||||
if (!uri.match(/^\S+$/)) {
|
||||
throw new Error('Illegal token, must be a set of non-space characters');
|
||||
}
|
||||
|
||||
if (!issuers.every(issuer => issuer.match(/^\S+$/))) {
|
||||
throw new Error('Illegal issuer, must be a set of non-space characters');
|
||||
}
|
||||
|
||||
this.#entries.push({ algorithms, audiences, issuers, uri });
|
||||
}
|
||||
|
||||
async verifyToken(token: string) {
|
||||
// not sure if we would need to support multiple jwks entries, but implementing to match static/legacy token handlers
|
||||
for (const entry of this.#entries) {
|
||||
try {
|
||||
const jwks = createRemoteJWKSet(new URL(entry.uri));
|
||||
const {
|
||||
payload: { sub },
|
||||
} = await jwtVerify(token, jwks, {
|
||||
algorithms: entry.algorithms,
|
||||
issuer: entry.issuers,
|
||||
audience: entry.audiences,
|
||||
});
|
||||
|
||||
if (sub) {
|
||||
return { subject: sub };
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user