Added configurable signing algorithm

Signed-off-by: Manuel Scurti <manuel.scurti@agilelab.it>
This commit is contained in:
Manuel Scurti
2022-05-17 19:40:15 +02:00
parent 1b1b7d7201
commit f6aae90e4e
5 changed files with 130 additions and 27 deletions
+52 -4
View File
@@ -13,20 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import {
SignJWT,
generateKeyPair,
decodeProtectedHeader,
exportJWK,
generateKeyPair,
SignJWT,
} from 'jose';
import { cloneDeep } from 'lodash';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { IdentityClient } from './IdentityClient';
import { v4 as uuid } from 'uuid';
import { IdentityClient } from './IdentityClient';
interface AnyJWK extends Record<string, string> {
use: 'sig';
alg: string;
@@ -112,6 +112,54 @@ describe('IdentityClient', () => {
});
});
describe('identity client configuration', () => {
beforeEach(() => {
server.use(
rest.get(
`${mockBaseUrl}/.well-known/jwks.json`,
async (_, res, ctx) => {
const keys = await factory.listPublicKeys();
return res(ctx.json(keys));
},
),
);
});
it('should defaults to ES256 when no algorithm is supplied', async () => {
const identityClient = IdentityClient.create({
discovery,
issuer: mockBaseUrl,
});
const token = await factory.issueToken({ claims: { sub: 'foo' } });
const response = await identityClient.authenticate(token);
// expect that the authenticate is able to validate a token with ES256, which is the one set to FakeTokenFactory.
// This means that IdentityClient set ES256 by default.
expect(response).toEqual({
token: token,
identity: {
type: 'user',
userEntityRef: 'foo',
ownershipEntityRefs: [],
},
});
});
it('should throw error on empty algorithm string', async () => {
const identityClient = IdentityClient.create({
discovery,
issuer: mockBaseUrl,
algorithm: '',
});
const token = await factory.issueToken({ claims: { sub: 'foo' } });
return expect(
async () => await identityClient.authenticate(token),
).rejects.toThrow();
});
});
describe('authenticate', () => {
beforeEach(() => {
server.use(
+18 -12
View File
@@ -13,22 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { AuthenticationError } from '@backstage/errors';
import {
createRemoteJWKSet,
decodeJwt,
jwtVerify,
decodeProtectedHeader,
FlattenedJWSInput,
JWSHeaderParameters,
decodeProtectedHeader,
jwtVerify,
} from 'jose';
import { GetKeyFunction } from 'jose/dist/types/types';
import { BackstageIdentityResponse } from './types';
const CLOCK_MARGIN_S = 10;
export type IdentityClientOptions = {
discovery: PluginEndpointDiscovery;
issuer: string;
/** JWS "alg" (Algorithm) Header Parameter value. Defaults to ES256.
* Must match the algorithm defined in TokenFactory.
* More info on supported algorithms: https://github.com/panva/jose */
algorithm?: string;
};
/**
* An identity client to interact with auth-backend and authenticate Backstage
* tokens
@@ -39,25 +49,21 @@ const CLOCK_MARGIN_S = 10;
export class IdentityClient {
private readonly discovery: PluginEndpointDiscovery;
private readonly issuer: string;
private readonly algorithm: string;
private keyStore?: GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput>;
private keyStoreUpdated: number = 0;
/**
* Create a new {@link IdentityClient} instance.
*/
static create(options: {
discovery: PluginEndpointDiscovery;
issuer: string;
}): IdentityClient {
static create(options: IdentityClientOptions): IdentityClient {
return new IdentityClient(options);
}
private constructor(options: {
discovery: PluginEndpointDiscovery;
issuer: string;
}) {
private constructor(options: IdentityClientOptions) {
this.discovery = options.discovery;
this.issuer = options.issuer;
this.algorithm = options.algorithm ?? 'ES256';
}
/**
@@ -82,7 +88,7 @@ export class IdentityClient {
throw new AuthenticationError('No keystore exists');
}
const decoded = await jwtVerify(token, this.keyStore, {
algorithms: ['ES256'],
algorithms: [this.algorithm],
audience: 'backstage',
issuer: this.issuer,
});